Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c3f7018fb | |||
| a100cede46 | |||
| 2466fd67d3 | |||
| 3d528e3bbf | |||
| a629bedccf | |||
| 4f566b1957 | |||
| d9c847c557 | |||
| 1bf5b526be | |||
| 9275fbe04f | |||
| eb122d52a9 |
@@ -25,7 +25,8 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
|
|||||||
|
|
||||||
## Основные файлы
|
## Основные файлы
|
||||||
|
|
||||||
- `src/scenes/streaming/streaming_world_loader.gd` - главный runtime streamer мира.
|
- `src/render/world_render_facade.gd` - стабильная граница runtime/tools для focus и metrics.
|
||||||
|
- `src/scenes/streaming/streaming_world_loader.gd` - внутренний runtime streamer мира.
|
||||||
- `src/scenes/streaming/eastern_kingdoms_streaming.tscn` - текущая сцена для проверки Azeroth/Eastern Kingdoms.
|
- `src/scenes/streaming/eastern_kingdoms_streaming.tscn` - текущая сцена для проверки Azeroth/Eastern Kingdoms.
|
||||||
- `addons/mpq_extractor/loaders/adt_builder.gd` - сборка ADT terrain, control splat материалов и MH2O liquids.
|
- `addons/mpq_extractor/loaders/adt_builder.gd` - сборка ADT terrain, control splat материалов и MH2O liquids.
|
||||||
- `addons/mpq_extractor/loaders/m2_builder.gd` - сборка статического M2 mesh/material.
|
- `addons/mpq_extractor/loaders/m2_builder.gd` - сборка статического M2 mesh/material.
|
||||||
@@ -873,6 +874,21 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
|||||||
- This closes a coordinate/renderer integration criterion only. It does not
|
- This closes a coordinate/renderer integration criterion only. It does not
|
||||||
claim TrinityCore row equivalence or visual parity with the original client.
|
claim TrinityCore row equivalence or visual parity with the original client.
|
||||||
|
|
||||||
|
## 2026-07-15 WorldRenderFacade First Seam
|
||||||
|
|
||||||
|
- Runtime streaming scenes, checkpoint capture and renderer probes now submit
|
||||||
|
typed focus through `WorldRenderFacade`; those callers no longer call the
|
||||||
|
monolithic streamer's focus methods or configure its focus source directly.
|
||||||
|
- Baseline capture reads queue/cache/activity counts through
|
||||||
|
`renderer_metrics_snapshot()`. The facade returns a deep copy, so diagnostics
|
||||||
|
cannot mutate renderer-owned state.
|
||||||
|
- `StreamingWorldLoader` remains the internal implementation and still owns all
|
||||||
|
queues, tasks, caches, nodes and RIDs. The facade adds no parallel state or
|
||||||
|
replacement scheduler.
|
||||||
|
- This package does not alter targets, budgets, cache versions, placement,
|
||||||
|
visibility, materials or animation. Environment, entity visuals and ground
|
||||||
|
query remain explicit follow-up facade contracts in M03.
|
||||||
|
|
||||||
## Practical Rule For Future Work
|
## Practical Rule For Future Work
|
||||||
|
|
||||||
If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it.
|
If something improves quality but creates visible hitch, it is not done. Move it to bake/cache/background work, split finalization over frames, or prewarm it before the player can see it.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- OPENWC_CLAIM:M02-GMP-INPUT-001:sindo-main-codex:2026-07-16 -->
|
<!-- OPENWC_CLAIM:M02-GMP-INPUT-001:sindo-main-codex:2026-07-16 -->
|
||||||
<!-- OPENWC_HANDOFF:READY:M02-GMP-INPUT-001:6bd2e84 -->
|
<!-- OPENWC_HANDOFF:READY:M02-GMP-INPUT-001:6bd2e84 -->
|
||||||
|
<!-- OPENWC_INTEGRATION:ACCEPTED:M02-GMP-INPUT-001:14dead1 -->
|
||||||
|
|
||||||
## Ownership
|
## Ownership
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ turn consumption at the server-contract default turn rate.
|
|||||||
- Blocked by:
|
- Blocked by:
|
||||||
|
|
||||||
<!-- OPENWC_HANDOFF:READY:M02-GMP-INPUT-PROFILE-335-001:8776a6b -->
|
<!-- OPENWC_HANDOFF:READY:M02-GMP-INPUT-PROFILE-335-001:8776a6b -->
|
||||||
|
<!-- OPENWC_INTEGRATION:ACCEPTED:M02-GMP-INPUT-PROFILE-335-001:eb122d5 -->
|
||||||
|
|
||||||
## Handoff
|
## Handoff
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- OPENWC_CLAIM:M02-GMP-MOVEMENT-001:sindo-main-codex:2026-07-16 -->
|
<!-- OPENWC_CLAIM:M02-GMP-MOVEMENT-001:sindo-main-codex:2026-07-16 -->
|
||||||
<!-- OPENWC_HANDOFF:READY:M02-GMP-MOVEMENT-001:435e1c9 -->
|
<!-- OPENWC_HANDOFF:READY:M02-GMP-MOVEMENT-001:435e1c9 -->
|
||||||
|
<!-- OPENWC_INTEGRATION:ACCEPTED:M02-GMP-MOVEMENT-001:5206c42 -->
|
||||||
|
|
||||||
## Ownership
|
## Ownership
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ explicit `RenderSandbox` profile and are rejected by `Blizzlike335`.
|
|||||||
- Blocked by:
|
- Blocked by:
|
||||||
|
|
||||||
<!-- OPENWC_HANDOFF:READY:M02-GMP-SANDBOX-CAPABILITIES-001:742c415 -->
|
<!-- OPENWC_HANDOFF:READY:M02-GMP-SANDBOX-CAPABILITIES-001:742c415 -->
|
||||||
|
<!-- OPENWC_INTEGRATION:ACCEPTED:M02-GMP-SANDBOX-CAPABILITIES-001:c3094c9 -->
|
||||||
|
|
||||||
## Handoff
|
## Handoff
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<!-- OPENWC_CLAIM:M02-GMP-TERRAIN-QUERY-001:sindo-main-codex:2026-07-16 -->
|
<!-- OPENWC_CLAIM:M02-GMP-TERRAIN-QUERY-001:sindo-main-codex:2026-07-16 -->
|
||||||
<!-- OPENWC_HANDOFF:READY:M02-GMP-TERRAIN-QUERY-001:a45d521 -->
|
<!-- OPENWC_HANDOFF:READY:M02-GMP-TERRAIN-QUERY-001:a45d521 -->
|
||||||
|
<!-- OPENWC_INTEGRATION:ACCEPTED:M02-GMP-TERRAIN-QUERY-001:f5bb64e -->
|
||||||
|
|
||||||
## Ownership
|
## Ownership
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# M02-QAR-INTEGRATOR-CLOSEOUT-001 — M02 milestone closeout
|
||||||
|
|
||||||
|
<!-- OPENWC_CLAIM:M02-QAR-INTEGRATOR-CLOSEOUT-001:sindo-main-codex:2026-07-17 -->
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
- Target: M02
|
||||||
|
- Program: QAR
|
||||||
|
- Owner/Agent ID: sindo-main-codex
|
||||||
|
- Branch: `work/sindo-main-codex/m02-integrator-closeout`
|
||||||
|
- Lease expires UTC: 2026-07-17
|
||||||
|
- Integrator: sindo-main-codex, continuing the user-directed milestone workflow on 2026-07-15
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
Audit every merged M02 package on current master, record final structural and
|
||||||
|
fidelity evidence, close M02 and activate M03 only when all target and repository
|
||||||
|
gates pass.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Change player, movement, terrain, camera, presentation or renderer behavior.
|
||||||
|
- Claim complete WoW 3.3.5a locomotion/camera parity.
|
||||||
|
- Implement recorded jump/fall/swim/fly, collision or timing gaps.
|
||||||
|
- Commit proprietary assets, generated UIDs, caches or user scene changes.
|
||||||
|
|
||||||
|
## Paths
|
||||||
|
|
||||||
|
- Exclusive: M02/M03 target status, M02 final Evidence, all M02 claim acceptance markers
|
||||||
|
- Shared/hotspots: `targets/README.md`, `targets/DEVELOPMENT_ROADMAP.md`, this claim
|
||||||
|
- Generated/ignored: local Godot imports, native DLL, renderer corpus and `user://` reports
|
||||||
|
|
||||||
|
## Contracts and data
|
||||||
|
|
||||||
|
- Runtime APIs, Input Map actions, fixture schemas and cache formats: unchanged
|
||||||
|
- Consumers: milestone sequence and future M03 renderer-facade packages
|
||||||
|
- Fidelity classification distinguishes direct build-12340 binding evidence,
|
||||||
|
server/reference compatibility evidence and explicitly unverified gaps
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Requires: all seven M02 READY packages integrated through master `eb122d5`
|
||||||
|
- Blocks: M03 activation
|
||||||
|
- External state: private original-client files are hash/provenance inputs only
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Commands: all M02 input/movement/capability/terrain/camera/presentation tests,
|
||||||
|
coordinate/streaming boundaries, unified renderer dry-run, documentation,
|
||||||
|
coordination and diff hygiene
|
||||||
|
- Fidelity evidence: build-12340 default binding hashes and selected facts;
|
||||||
|
pinned TrinityCore turn rate; public reference routing; explicit remaining gaps
|
||||||
|
- Performance: no runtime change; renderer baseline remains the performance guard
|
||||||
|
|
||||||
|
## Documentation deliverables
|
||||||
|
|
||||||
|
- M02 final target Evidence/checklist and limitation statement
|
||||||
|
- M02 claim integration-acceptance markers
|
||||||
|
- Target index and roadmap transition to M03
|
||||||
|
- Existing API/module specifications and diagrams audited without behavioral edits
|
||||||
|
|
||||||
|
## Simplicity and naming
|
||||||
|
|
||||||
|
- Important names introduced: none beyond the closeout work-package ID
|
||||||
|
- Simplest approach: evidence/status-only integrator patch
|
||||||
|
- Rejected complexity: behavioral fixes or a new closeout framework
|
||||||
|
- Unavoidable complexity: explicit evidence classification prevents false 1:1 claims
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
- State: ready-for-review
|
||||||
|
- Done: accepted seven M02 packages, reran post-merge gates, recorded complete
|
||||||
|
target Evidence, closed M02 and activated M03
|
||||||
|
- Next: merge closeout commit `1bf5b52` before starting an M03 implementation claim
|
||||||
|
- Blocked by:
|
||||||
|
|
||||||
|
<!-- OPENWC_HANDOFF:READY:M02-QAR-INTEGRATOR-CLOSEOUT-001:1bf5b52 -->
|
||||||
|
|
||||||
|
## Handoff
|
||||||
|
|
||||||
|
- Commit: `1bf5b52`.
|
||||||
|
- Branch: `work/sindo-main-codex/m02-integrator-closeout`.
|
||||||
|
- Outcome: M02 is `DONE` with all eight structural checklist items and complete
|
||||||
|
classified Evidence; M03 is the only `ACTIVE` target and the target index/
|
||||||
|
roadmap point to renderer-facade extraction.
|
||||||
|
- Integration audit: all seven READY implementation commits are ancestors of
|
||||||
|
merged master `eb122d5`; their claims now carry the corresponding
|
||||||
|
`OPENWC_INTEGRATION:ACCEPTED` merge markers.
|
||||||
|
- Verification: input `16/9/3/1/3`, movement `15` cases and `2` transitions,
|
||||||
|
capability `3` profiles and `2` scenes, terrain `4/1/1/2`, camera `12/3`,
|
||||||
|
presentation `9/10/3`, coordinate boundaries `103` files and StreamingFocus
|
||||||
|
`2` runtime scenes/`3` capture tools all passed. Unified renderer dry-run
|
||||||
|
passed project/material/dedupe/shutdown gates, manifest `7/7/7`, calibration
|
||||||
|
maximum error `0.000015`, server-spawn renderer and seven checkpoint plans.
|
||||||
|
- Repository gates: documentation passed with `8` module specs and `7` required
|
||||||
|
files. Coordination passed with one active target; `13` expired M00 claim
|
||||||
|
warnings are historical and unrelated to M02.
|
||||||
|
- Fidelity: direct build-12340 binding hashes/facts are preserved in the
|
||||||
|
sanitized fixture. TrinityCore turn rate and WoWee mouse routing remain
|
||||||
|
explicitly reference-only. Translation speeds/camera values are sandbox
|
||||||
|
regressions, and jump/fall/swim/fly parity remains unimplemented/unverified.
|
||||||
|
Debug sprint/free flight is negatively proven absent from `Blizzlike335`.
|
||||||
|
- Remaining risks: target Evidence carries exact follow-up scope for M09/M10/M12:
|
||||||
|
movement/camera timing, jump/fall/swim/fly, autorun/LMB semantics, active
|
||||||
|
collision, terrain/liquid policy, persisted bindings, server authority and
|
||||||
|
prediction/reconciliation. No complete `1:1` claim is made.
|
||||||
|
- Runtime/cache/migration: no behavioral change, dependency, cache schema or
|
||||||
|
persisted settings migration in closeout.
|
||||||
|
- Documentation: M02 target Evidence/checklist, seven acceptance markers,
|
||||||
|
target index and roadmap state updated. Existing input, movement, terrain,
|
||||||
|
camera and presentation API/module specs and diagrams passed the documentation gate.
|
||||||
@@ -90,6 +90,7 @@ no-collision sandbox baseline.
|
|||||||
- Blocked by:
|
- Blocked by:
|
||||||
|
|
||||||
<!-- OPENWC_HANDOFF:READY:M02-RND-CAMERA-001:8c1cf9b -->
|
<!-- OPENWC_HANDOFF:READY:M02-RND-CAMERA-001:8c1cf9b -->
|
||||||
|
<!-- OPENWC_INTEGRATION:ACCEPTED:M02-RND-CAMERA-001:68e6f60 -->
|
||||||
|
|
||||||
## Handoff
|
## Handoff
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ components without changing the current sandbox presentation.
|
|||||||
- Blocked by:
|
- Blocked by:
|
||||||
|
|
||||||
<!-- OPENWC_HANDOFF:READY:M02-RND-CHARACTER-PRESENTATION-001:97bb53f -->
|
<!-- OPENWC_HANDOFF:READY:M02-RND-CHARACTER-PRESENTATION-001:97bb53f -->
|
||||||
|
<!-- OPENWC_INTEGRATION:ACCEPTED:M02-RND-CHARACTER-PRESENTATION-001:8c71e46 -->
|
||||||
|
|
||||||
## Handoff
|
## Handoff
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# M03-RND-FACADE-001 — Renderer facade first seam
|
||||||
|
|
||||||
|
<!-- OPENWC_CLAIM:M03-RND-FACADE-001:sindo-main-codex:2026-07-17 -->
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
- Target: M03
|
||||||
|
- Program: RND
|
||||||
|
- Owner/Agent ID: sindo-main-codex
|
||||||
|
- Branch: `work/sindo-main-codex/m03-renderer-facade`
|
||||||
|
- Lease expires UTC: 2026-07-17
|
||||||
|
- Integrator: M03 milestone integrator
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
Introduce `WorldRenderFacade` as the only runtime/tool caller surface for typed
|
||||||
|
streaming focus updates and read-only renderer metrics, while retaining
|
||||||
|
`StreamingWorldLoader` as the unchanged internal implementation.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Extract the pure target planner or budget scheduler in this package.
|
||||||
|
- Add environment or entity-presentation commands before their contracts exist.
|
||||||
|
- Replace the M02 `TerrainQuery` gameplay boundary in this package.
|
||||||
|
- Change streaming radii, queues, cache formats, visuals or quality presets.
|
||||||
|
- Mark M03 complete or edit its checklist/Evidence.
|
||||||
|
|
||||||
|
## Paths
|
||||||
|
|
||||||
|
- Exclusive: `src/render/world_render_facade.gd`,
|
||||||
|
`src/tools/verify_world_render_facade.gd`, this claim
|
||||||
|
- Shared/hotspots: both runtime streaming scenes, three renderer capture/probe
|
||||||
|
tools, `src/tools/verify_streaming_focus.gd`, `docs/modules/world-renderer.md`,
|
||||||
|
`docs/modules/README.md`, `RENDER.md`
|
||||||
|
- Generated/ignored: local `.godot`, native build outputs, caches and proprietary
|
||||||
|
renderer corpus
|
||||||
|
|
||||||
|
## Contracts and data
|
||||||
|
|
||||||
|
- Public API: `set_streaming_focus`, `refresh_streaming_focus`,
|
||||||
|
`renderer_metrics_snapshot`
|
||||||
|
- Scene adapter: explicit `streaming_focus_source_path` owned by the facade
|
||||||
|
- Internal implementation seam: `streaming_world_loader_path`; callers must not
|
||||||
|
read streamer queues or call its methods directly
|
||||||
|
- Schema/cache/coordinate contracts: unchanged
|
||||||
|
- Consumers: runtime scenes, capture tool and renderer probes
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Requires: M01 `StreamingFocus` and merged M02 closeout on master `4f566b1`
|
||||||
|
- Blocks: environment/entity/ground facade contracts and planner extraction
|
||||||
|
- External state: none; contract verification uses a test double
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- Commands: dedicated facade verifier, streaming-focus regression, renderer
|
||||||
|
baseline manifest/dry-run, coordinate boundary and repository gates
|
||||||
|
- Fixtures: typed focus, source Node3D and read-only metrics test double
|
||||||
|
- Fidelity evidence: no renderer configuration, planner, queue, placement,
|
||||||
|
visibility, material, animation or cache behavior changes
|
||||||
|
- Performance budget: one source position sample per process frame; target
|
||||||
|
recomputation remains throttled inside the existing streamer
|
||||||
|
|
||||||
|
## Documentation deliverables
|
||||||
|
|
||||||
|
- Inline public API docs for facade properties and methods
|
||||||
|
- Updated world-renderer module specification
|
||||||
|
- Updated data-flow, sequence and dependency diagrams
|
||||||
|
- Updated source map/status and renderer notes
|
||||||
|
|
||||||
|
## Simplicity and naming
|
||||||
|
|
||||||
|
- Important names: `WorldRenderFacade`, `renderer_metrics_snapshot`
|
||||||
|
- Simplest approach: one small Node adapter delegating to the existing streamer
|
||||||
|
- Rejected complexity: service container, generic command bus, streamer rewrite
|
||||||
|
or duplicate queue/cache state
|
||||||
|
- Unavoidable complexity: scene-relative source and implementation paths are
|
||||||
|
resolved on the main thread with explicit diagnostics
|
||||||
|
- Measured optimization evidence: not applicable; behavior-preserving seam
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
- State: ready-for-review
|
||||||
|
- Done: introduced the facade, migrated two runtime scenes and three renderer
|
||||||
|
tools, added regression coverage and updated renderer documentation
|
||||||
|
- Next: M03 integrator reviews and merges `3d528e3`
|
||||||
|
- Blocked by:
|
||||||
|
|
||||||
|
<!-- OPENWC_HANDOFF:READY:M03-RND-FACADE-001:3d528e3 -->
|
||||||
|
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-FACADE-001:a100ced -->
|
||||||
|
|
||||||
|
## Handoff
|
||||||
|
|
||||||
|
- Commit: `3d528e3`
|
||||||
|
- Integration: accepted in master merge `a100ced`
|
||||||
|
- Results: `WorldRenderFacade` is the runtime/tool surface for typed focus and
|
||||||
|
detached metrics; the existing streamer remains the sole owner of queues,
|
||||||
|
caches, tasks, nodes and RIDs.
|
||||||
|
- Verification: facade `delegation=3 runtime_scenes=2 tools=3`; streaming focus
|
||||||
|
`contract=1 runtime_scenes=2 capture_tools=3`; coordinate boundary
|
||||||
|
`files=105 native_boundary=1 oracle=1 consumers=5 classifier_cases=6`;
|
||||||
|
render manifest `checkpoints=7 coverage=7 caches=7`; seven-checkpoint headless
|
||||||
|
dry-run; coordination/documentation/diff gates passed.
|
||||||
|
- Fidelity: no changes to renderer target calculation, budgets, queue order,
|
||||||
|
cache versions, placement, visibility, materials or animation. The dry-run
|
||||||
|
retained all seven checkpoint plans. No build-12340 parity claim is added.
|
||||||
|
- External/local inputs: ignored native DLL and generated ADT resource scripts
|
||||||
|
were copied from master only to run the worktree smoke. Proprietary extracted
|
||||||
|
ADT and character GLB data were unavailable, producing the expected dry-run
|
||||||
|
degraded-data diagnostics.
|
||||||
|
- Remaining risks: environment, entity visuals and ground query are not yet
|
||||||
|
facade contracts; streamer exported configuration remains transitional scene
|
||||||
|
composition; focus-source sampling adds one small main-thread adapter step per
|
||||||
|
process frame while target refresh remains streamer-throttled.
|
||||||
|
- Documentation updated: inline facade API, `docs/modules/world-renderer.md`
|
||||||
|
public API/inputs/outputs/data-flow/sequence/ownership/recovery/status/source
|
||||||
|
map, and `RENDER.md` file map plus implementation note.
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
| Field | Value |
|
| Field | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Status | Partial |
|
| Status | Partial |
|
||||||
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; декомпозиция M02–M03 |
|
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; `M03-RND-FACADE-001` |
|
||||||
| Owners | Renderer workstream / milestone integrator |
|
| Owners | Renderer workstream / milestone integrator |
|
||||||
| Last verified | Worktree `work/sindo-main-codex/m01-server-spawn-renderer`, 2026-07-13 |
|
| Last verified | Worktree `work/sindo-main-codex/m03-renderer-facade`, 2026-07-15 |
|
||||||
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
|
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
@@ -32,7 +32,8 @@ flowchart LR
|
|||||||
SpawnFixture[Pinned server spawn fixture] --> SpawnManifest[Spawn render manifest]
|
SpawnFixture[Pinned server spawn fixture] --> SpawnManifest[Spawn render manifest]
|
||||||
SpawnManifest --> Capture
|
SpawnManifest --> Capture
|
||||||
Adapter --> Focus
|
Adapter --> Focus
|
||||||
Focus --> Loader[StreamingWorldLoader]
|
Focus --> Facade[WorldRenderFacade]
|
||||||
|
Facade --> Loader[StreamingWorldLoader internal]
|
||||||
Assets[Extracted WDT/ADT/M2/WMO/BLP] --> Native[Native loaders]
|
Assets[Extracted WDT/ADT/M2/WMO/BLP] --> Native[Native loaders]
|
||||||
Cache[Baked terrain/M2/WMO caches] --> Loader
|
Cache[Baked terrain/M2/WMO caches] --> Loader
|
||||||
Native --> Parsed[Parsed tile/model data]
|
Native --> Parsed[Parsed tile/model data]
|
||||||
@@ -60,36 +61,43 @@ Forbidden dependencies:
|
|||||||
|
|
||||||
## Public API
|
## Public API
|
||||||
|
|
||||||
Текущая система ещё не имеет стабильного facade. Фактический integration surface — `StreamingWorldLoader` Node3D и exported properties. M03 должен заменить этот surface на `WorldRenderFacade`.
|
Runtime, capture and probe callers use `WorldRenderFacade` for focus and metrics.
|
||||||
|
`StreamingWorldLoader` remains the internal scene implementation while later M03
|
||||||
|
packages add environment, entity-visual and ground-query contracts.
|
||||||
|
|
||||||
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `map_name` | Exported property | WDT/map directory name | Set before world load | Missing WDT produces diagnostic/empty world |
|
| `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 |
|
| `extracted_dir` | Exported property | Root of legally extracted client data | Session lifetime | Missing files reported per loader path |
|
||||||
| `StreamingFocus` | Immutable value | Typed Godot-basis position without Node/Camera dependency | Any thread; caller-owned reference | Null position is a caller contract violation |
|
| `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 |
|
| `WorldRenderFacade.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 |
|
| `WorldRenderFacade.set_streaming_focus` | Public method | Accepts focus from app/editor/tool composition | Main thread/session | Missing internal renderer is diagnosed; no state is duplicated |
|
||||||
| `refresh_streaming_focus` | Public method | Samples configured source and applies refresh thresholds | Main thread/session | Returns `false` when no valid focus exists |
|
| `WorldRenderFacade.refresh_streaming_focus` | Public method | Samples configured source and delegates existing refresh thresholds | Main thread/session | Returns `false` when dependency/focus is unavailable |
|
||||||
|
| `WorldRenderFacade.renderer_metrics_snapshot` | Public method | Returns a deep-detached queue/cache/activity snapshot | Main thread/caller-owned result | Missing/invalid implementation result returns empty snapshot with diagnostic |
|
||||||
|
| `WorldRenderFacade.streaming_world_loader_path` | Internal adapter property | Resolves the existing implementation without exposing it to consumers | Main thread, scene lifetime | Missing method/path produces one diagnostic until recovery |
|
||||||
| `camera_path` | Exported property | Camera used only by optional automatic overview positioning | Main thread, scene lifetime | Missing camera skips automatic positioning |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `hitch_profiler_enabled` | Exported property | Enables named hitch sections | Runtime mutable | Logging overhead only |
|
||||||
|
|
||||||
Публичным contract не считаются внутренние dictionaries, queues, job records и generated node names.
|
Публичным contract не считаются `StreamingWorldLoader` methods/properties,
|
||||||
|
внутренние dictionaries, queues, job records и generated node names. Scene-owned
|
||||||
|
loader configuration remains transitional composition data, not a caller API.
|
||||||
|
|
||||||
## Inputs and outputs
|
## Inputs and outputs
|
||||||
|
|
||||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| Input | `StreamingFocus` | Player/spectator/editor/capture adapter | `StreamingWorldLoader` | Immutable caller value | Main thread/session |
|
| Input | `StreamingFocus` | Player/spectator/editor/capture adapter | `WorldRenderFacade` | 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 | Focus-source `NodePath` | Runtime/test scene composition | `WorldRenderFacade` 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 | 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 | 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 |
|
| Input | Baked resources | Cache build tools | Streamer/builders | Shared immutable resource/cache entry | Session/cache lifetime |
|
||||||
| Test input | Server-spawn render manifest | Coordinate fixture/verifier | Checkpoint capture tool | Repository-owned diagnostic data | Test-process lifetime |
|
| Test input | Server-spawn render manifest | Coordinate fixture/verifier | Checkpoint capture tool | Repository-owned diagnostic data | Test-process lifetime |
|
||||||
| Output | Desired tile/detail operations | Streaming planner inside loader | Finalize queues | Loader-owned | Cross-frame |
|
| Output | Desired tile/detail operations | Streaming planner inside loader | Finalize queues | Loader-owned | Cross-frame |
|
||||||
| Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach |
|
| Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach |
|
||||||
| Output | Metrics/diagnostics | Hitch/stat instrumentation | Logs/baseline tools | Immutable records | Periodic/frame |
|
| Output | Detached renderer metrics | `WorldRenderFacade` | Capture/baseline tools | Caller-owned deep copy | On demand |
|
||||||
|
| Output | Logs/diagnostics | Hitch/stat instrumentation | Logs/baseline tools | Immutable records | Periodic/frame |
|
||||||
| Test output | Diagnostic spawn marker and cold/warm PNGs | Checkpoint capture tool | Human/integrator review | SceneTree marker; `user://` files | One capture process |
|
| Test output | Diagnostic spawn marker and cold/warm PNGs | Checkpoint capture tool | Human/integrator review | SceneTree marker; `user://` files | One capture process |
|
||||||
|
|
||||||
Side effects:
|
Side effects:
|
||||||
@@ -104,7 +112,7 @@ Side effects:
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
P[Player/spectator Node3D] --> A[Explicit scene adapter]
|
PlayerSource[Player/spectator Node3D] --> A[Explicit scene adapter]
|
||||||
C[Capture camera] --> A
|
C[Capture camera] --> A
|
||||||
SF[Pinned server spawn] --> SM[Validated spawn manifest]
|
SF[Pinned server spawn] --> SM[Validated spawn manifest]
|
||||||
SM --> C
|
SM --> C
|
||||||
@@ -112,11 +120,12 @@ flowchart TD
|
|||||||
E[Editor viewport] --> EA[Editor adapter]
|
E[Editor viewport] --> EA[Editor adapter]
|
||||||
A --> F[StreamingFocus]
|
A --> F[StreamingFocus]
|
||||||
EA --> F
|
EA --> F
|
||||||
F --> T[Compute desired ADT tiles]
|
F --> RF[WorldRenderFacade]
|
||||||
|
RF --> T[StreamingWorldLoader target refresh]
|
||||||
WDT[WDT tile catalog] --> T
|
WDT[WDT tile catalog] --> T
|
||||||
T --> Q[Tile load queue]
|
T --> Q[Tile load queue]
|
||||||
Q --> P[Worker parse/cache load]
|
Q --> Parse[Worker parse/cache load]
|
||||||
P --> R[Result queues]
|
Parse --> R[Result queues]
|
||||||
R --> B[Per-frame budget scheduler]
|
R --> B[Per-frame budget scheduler]
|
||||||
B --> Terrain[Terrain attach/upgrade]
|
B --> Terrain[Terrain attach/upgrade]
|
||||||
B --> M2[M2 group/MultiMesh attach]
|
B --> M2[M2 group/MultiMesh attach]
|
||||||
@@ -155,12 +164,14 @@ stateDiagram-v2
|
|||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant Source as Player/Editor/Capture source
|
participant Source as Player/Editor/Capture source
|
||||||
participant Focus as StreamingFocus adapter
|
participant Focus as StreamingFocus adapter
|
||||||
participant Stream as StreamingWorldLoader
|
participant Facade as WorldRenderFacade
|
||||||
|
participant Stream as StreamingWorldLoader internal
|
||||||
participant Worker as Worker task
|
participant Worker as Worker task
|
||||||
participant Budget as Main-thread budget
|
participant Budget as Main-thread budget
|
||||||
participant Render as SceneTree/RenderingServer
|
participant Render as SceneTree/RenderingServer
|
||||||
Source->>Focus: sample explicit Godot position
|
Source->>Focus: sample explicit Godot position
|
||||||
Focus->>Stream: set/refresh typed focus
|
Focus->>Facade: set/refresh typed focus
|
||||||
|
Facade->>Stream: delegate focus and refresh
|
||||||
Stream->>Stream: desired tiles and priorities
|
Stream->>Stream: desired tiles and priorities
|
||||||
Stream->>Worker: parse/load tile and detail data
|
Stream->>Worker: parse/load tile and detail data
|
||||||
Worker-->>Stream: immutable result
|
Worker-->>Stream: immutable result
|
||||||
@@ -176,6 +187,8 @@ sequenceDiagram
|
|||||||
## Ownership, threading and resources
|
## Ownership, threading and resources
|
||||||
|
|
||||||
- `StreamingWorldLoader` владеет tile states, queues, task registries, active world instances и render caches session scope.
|
- `StreamingWorldLoader` владеет tile states, queues, task registries, active world instances и render caches session scope.
|
||||||
|
- `WorldRenderFacade` owns only scene-relative adapter paths. It does not own the
|
||||||
|
streamer, focus source, returned metrics, queues, caches, nodes or RIDs.
|
||||||
- Focus producers own the immutable `StreamingFocus` reference; the loader retains
|
- Focus producers own the immutable `StreamingFocus` reference; the loader retains
|
||||||
the latest reference and converts it to `Vector3` only inside the render boundary.
|
the latest reference and converts it to `Vector3` only inside the render boundary.
|
||||||
- Worker tasks не должны менять SceneTree и shared Resource concurrently.
|
- Worker tasks не должны менять SceneTree и shared Resource concurrently.
|
||||||
@@ -192,6 +205,8 @@ sequenceDiagram
|
|||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| Missing WDT/ADT | File/catalog check | Tile/map unavailable | Loader warning/error | Fix extraction/profile, reload |
|
| 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` |
|
| 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` |
|
||||||
|
| Missing renderer implementation | Facade path/method validation | Focus/metrics call fails without mutation | One facade diagnostic until recovery | Fix scene composition path/implementation |
|
||||||
|
| Invalid metrics result | Facade return-type validation | Return empty caller snapshot | Facade error | Restore `Dictionary` implementation contract |
|
||||||
| No focus supplied | Refresh contract | No target recomputation; queues continue draining | `refresh_streaming_focus()` returns `false` | Supply player/editor/capture 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 |
|
| 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 |
|
| Worker failure | Task result/empty data | Skip/degrade affected asset | Queue/task warning | Retry/rebuild source |
|
||||||
@@ -225,6 +240,8 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
## Diagnostics and observability
|
## Diagnostics and observability
|
||||||
|
|
||||||
- Logs: `HITCH`, `PERF`, `TERRAIN_QUALITY`, `SKY_LIGHT`, `SKYBOX_MODEL`.
|
- Logs: `HITCH`, `PERF`, `TERRAIN_QUALITY`, `SKY_LIGHT`, `SKYBOX_MODEL`.
|
||||||
|
- API snapshot: `WorldRenderFacade.renderer_metrics_snapshot()` returns detached
|
||||||
|
queue/cache/activity counts without exposing mutable renderer state.
|
||||||
- Metrics: queue depths, active tiles/assets, named finalize times, frame percentiles and max hitch.
|
- Metrics: queue depths, active tiles/assets, named finalize times, frame percentiles and max hitch.
|
||||||
- Debug views: render checkpoint scenes/captures; future streaming/LOD/portal overlays.
|
- Debug views: render checkpoint scenes/captures; future streaming/LOD/portal overlays.
|
||||||
- Correlation IDs: currently mostly tile/path keys; target architecture adds session/job IDs.
|
- Correlation IDs: currently mostly tile/path keys; target architecture adds session/job IDs.
|
||||||
@@ -257,7 +274,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
| Character presentation | Prototype | Character creator/player experiment | Full customization/equipment states |
|
| 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 |
|
| 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 |
|
||||||
| Golden server-spawn visualization | Implemented | Pinned AzerothCore fixture, validated renderer manifest and diagnostic origin-marker capture at ADT `(32,48)`/MCNK `(3,12)` | No TrinityCore row or original-client visual-parity claim |
|
| Golden server-spawn visualization | Implemented | Pinned AzerothCore fixture, validated renderer manifest and diagnostic origin-marker capture at ADT `(32,48)`/MCNK `(3,12)` | No TrinityCore row or original-client visual-parity claim |
|
||||||
| Stable renderer facade | Planned | M03 | Current caller uses monolithic Node API |
|
| Stable renderer facade | Partial | `M03-RND-FACADE-001` contract/runtime/tool regressions | Focus and metrics migrated; environment/entity visuals/ground query remain |
|
||||||
|
|
||||||
## Known gaps and risks
|
## Known gaps and risks
|
||||||
|
|
||||||
@@ -283,9 +300,11 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
|
|
||||||
| Path | Responsibility |
|
| Path | Responsibility |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| `src/render/world_render_facade.gd` | Stable caller boundary for typed focus and detached renderer metrics |
|
||||||
| `src/scenes/streaming/streaming_world_loader.gd` | Current runtime orchestration, queues, caches and attachment |
|
| `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/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 |
|
| `src/tools/verify_streaming_focus.gd` | Headless contract, dependency and runtime/tool wiring regression |
|
||||||
|
| `src/tools/verify_world_render_facade.gd` | Facade delegation, snapshot isolation and consumer wiring regression |
|
||||||
| `addons/mpq_extractor/loaders/adt_builder.gd` | Terrain/material/liquid construction |
|
| `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_builder.gd` | Static M2 mesh/material construction |
|
||||||
| `addons/mpq_extractor/loaders/m2_native_animated_builder.gd` | Native animated M2 experiment |
|
| `addons/mpq_extractor/loaders/m2_native_animated_builder.gd` | Native animated M2 experiment |
|
||||||
|
|||||||
+1
-1
Submodule reference/open-realm updated: dfe316d3b0...c7ed0545c8
@@ -0,0 +1,110 @@
|
|||||||
|
@tool
|
||||||
|
class_name WorldRenderFacade
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
## Stable main-thread boundary between runtime/tool callers and the monolithic
|
||||||
|
## streaming renderer. The facade owns no queues, caches, nodes or RIDs.
|
||||||
|
|
||||||
|
const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd")
|
||||||
|
const STREAMING_FOCUS_SCRIPT := preload("res://src/domain/streaming/streaming_focus.gd")
|
||||||
|
|
||||||
|
## Internal renderer implementation resolved relative to this node.
|
||||||
|
## The referenced node remains owned by its scene parent.
|
||||||
|
@export var streaming_world_loader_path: NodePath = NodePath("..")
|
||||||
|
|
||||||
|
## Optional explicit Node3D source sampled in Godot world coordinates.
|
||||||
|
## Runtime scenes use the player; capture and probe tools replace it with their camera.
|
||||||
|
@export var streaming_focus_source_path: NodePath
|
||||||
|
|
||||||
|
var _streaming_world_loader: Node
|
||||||
|
var _missing_loader_reported := false
|
||||||
|
var _missing_focus_source_reported := false
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_resolve_streaming_world_loader()
|
||||||
|
set_process(streaming_focus_source_path != NodePath())
|
||||||
|
if streaming_focus_source_path != NodePath():
|
||||||
|
# Child nodes become ready before their parent StreamingWorldLoader. Only
|
||||||
|
# publish the initial focus here; the loader applies it after initialization.
|
||||||
|
_capture_streaming_focus_from_source()
|
||||||
|
|
||||||
|
|
||||||
|
func _process(_delta_seconds: float) -> void:
|
||||||
|
_capture_streaming_focus_from_source()
|
||||||
|
|
||||||
|
|
||||||
|
## Replaces the renderer focus. The immutable value remains caller-owned; the
|
||||||
|
## internal streamer retains the reference for its next throttled refresh.
|
||||||
|
func set_streaming_focus(streaming_focus: StreamingFocus) -> void:
|
||||||
|
var loader := _resolve_streaming_world_loader()
|
||||||
|
if loader == null:
|
||||||
|
return
|
||||||
|
loader.call("set_streaming_focus", streaming_focus)
|
||||||
|
|
||||||
|
|
||||||
|
## Samples the configured source and requests target refresh from the internal
|
||||||
|
## streamer. Returns false when either dependency or a valid focus is unavailable.
|
||||||
|
func refresh_streaming_focus(force: bool = false) -> bool:
|
||||||
|
_capture_streaming_focus_from_source()
|
||||||
|
var loader := _resolve_streaming_world_loader()
|
||||||
|
if loader == null:
|
||||||
|
return false
|
||||||
|
return bool(loader.call("refresh_streaming_focus", force))
|
||||||
|
|
||||||
|
|
||||||
|
## Returns a detached read-only metrics snapshot for diagnostics and baselines.
|
||||||
|
## Mutating the returned Dictionary cannot mutate renderer-owned state.
|
||||||
|
func renderer_metrics_snapshot() -> Dictionary:
|
||||||
|
var loader := _resolve_streaming_world_loader()
|
||||||
|
if loader == null:
|
||||||
|
return {}
|
||||||
|
var metrics_variant = loader.call("render_baseline_snapshot")
|
||||||
|
if not (metrics_variant is Dictionary):
|
||||||
|
push_error("WorldRenderFacade received a non-Dictionary renderer metrics snapshot.")
|
||||||
|
return {}
|
||||||
|
return (metrics_variant as Dictionary).duplicate(true)
|
||||||
|
|
||||||
|
|
||||||
|
func _capture_streaming_focus_from_source() -> void:
|
||||||
|
if streaming_focus_source_path == NodePath():
|
||||||
|
return
|
||||||
|
var focus_source := get_node_or_null(streaming_focus_source_path) as Node3D
|
||||||
|
if focus_source == null:
|
||||||
|
if not _missing_focus_source_reported:
|
||||||
|
push_warning("WorldRenderFacade focus source is missing or is not Node3D: %s" % streaming_focus_source_path)
|
||||||
|
_missing_focus_source_reported = true
|
||||||
|
return
|
||||||
|
|
||||||
|
_missing_focus_source_reported = false
|
||||||
|
var godot_position: Vector3 = focus_source.global_position
|
||||||
|
var typed_world_position = GODOT_WORLD_POSITION_SCRIPT.new(
|
||||||
|
godot_position.x,
|
||||||
|
godot_position.y,
|
||||||
|
godot_position.z
|
||||||
|
)
|
||||||
|
set_streaming_focus(STREAMING_FOCUS_SCRIPT.new(typed_world_position))
|
||||||
|
|
||||||
|
|
||||||
|
func _resolve_streaming_world_loader() -> Node:
|
||||||
|
if is_instance_valid(_streaming_world_loader):
|
||||||
|
return _streaming_world_loader
|
||||||
|
var candidate := get_node_or_null(streaming_world_loader_path)
|
||||||
|
if candidate == null:
|
||||||
|
if not _missing_loader_reported:
|
||||||
|
push_error("WorldRenderFacade cannot resolve StreamingWorldLoader at %s" % streaming_world_loader_path)
|
||||||
|
_missing_loader_reported = true
|
||||||
|
return null
|
||||||
|
for required_method in [
|
||||||
|
&"set_streaming_focus",
|
||||||
|
&"refresh_streaming_focus",
|
||||||
|
&"render_baseline_snapshot",
|
||||||
|
]:
|
||||||
|
if not candidate.has_method(required_method):
|
||||||
|
if not _missing_loader_reported:
|
||||||
|
push_error("WorldRenderFacade renderer implementation lacks %s" % required_method)
|
||||||
|
_missing_loader_reported = true
|
||||||
|
return null
|
||||||
|
_streaming_world_loader = candidate
|
||||||
|
_missing_loader_reported = false
|
||||||
|
return _streaming_world_loader
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://sxn3ai2ic18r
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
[ext_resource type="Script" uid="uid://yi6lawwjgocg" path="res://src/scenes/streaming/streaming_world_loader.gd" id="1_stream"]
|
[ext_resource type="Script" uid="uid://yi6lawwjgocg" path="res://src/scenes/streaming/streaming_world_loader.gd" id="1_stream"]
|
||||||
[ext_resource type="Script" uid="uid://ltn2ko5kxvo4" path="res://src/scenes/player/third_person_wow_controller.gd" id="2_player"]
|
[ext_resource type="Script" uid="uid://ltn2ko5kxvo4" path="res://src/scenes/player/third_person_wow_controller.gd" id="2_player"]
|
||||||
[ext_resource type="Script" uid="uid://didjth34ut0v1" path="res://src/scenes/sky/wow_sky_controller.gd" id="3_sky"]
|
[ext_resource type="Script" uid="uid://didjth34ut0v1" path="res://src/scenes/sky/wow_sky_controller.gd" id="3_sky"]
|
||||||
[ext_resource type="Script" path="res://src/scenes/player/third_person_camera_rig.gd" id="4_camera_rig"]
|
[ext_resource type="Script" uid="uid://cq60al4uw0mgo" path="res://src/scenes/player/third_person_camera_rig.gd" id="4_camera_rig"]
|
||||||
[ext_resource type="Script" path="res://src/scenes/character/character_appearance_presenter.gd" id="5_appearance"]
|
[ext_resource type="Script" uid="uid://bhqbo1ftrt3a3" path="res://src/scenes/character/character_appearance_presenter.gd" id="5_appearance"]
|
||||||
[ext_resource type="Script" path="res://src/scenes/character/character_animation_presenter.gd" id="6_animation"]
|
[ext_resource type="Script" uid="uid://c2jnn0e3mmymc" path="res://src/scenes/character/character_animation_presenter.gd" id="6_animation"]
|
||||||
|
[ext_resource type="Script" path="res://src/render/world_render_facade.gd" id="7_render_facade"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="PlayerCapsuleShape_1"]
|
[sub_resource type="CapsuleShape3D" id="PlayerCapsuleShape_1"]
|
||||||
radius = 0.45
|
radius = 0.45
|
||||||
@@ -56,7 +57,6 @@ fog_depth_end = 5200.0
|
|||||||
|
|
||||||
[node name="StreamingWorld" type="Node3D" unique_id=1063159974]
|
[node name="StreamingWorld" type="Node3D" unique_id=1063159974]
|
||||||
script = ExtResource("1_stream")
|
script = ExtResource("1_stream")
|
||||||
streaming_focus_source_path = NodePath("ThirdPersonPlayer")
|
|
||||||
camera_path = NodePath("ThirdPersonPlayer/CameraPivot/Camera3D")
|
camera_path = NodePath("ThirdPersonPlayer/CameraPivot/Camera3D")
|
||||||
quality_preset = "High"
|
quality_preset = "High"
|
||||||
update_interval = 0.1
|
update_interval = 0.1
|
||||||
@@ -94,11 +94,17 @@ wmo_visibility_range = 3600.0
|
|||||||
debug_streaming = true
|
debug_streaming = true
|
||||||
hitch_profiler_enabled = true
|
hitch_profiler_enabled = true
|
||||||
|
|
||||||
|
[node name="WorldRenderFacade" type="Node" parent="."]
|
||||||
|
script = ExtResource("7_render_facade")
|
||||||
|
streaming_world_loader_path = NodePath("..")
|
||||||
|
streaming_focus_source_path = NodePath("../ThirdPersonPlayer")
|
||||||
|
|
||||||
[node name="ThirdPersonPlayer" type="CharacterBody3D" parent="." unique_id=502573687]
|
[node name="ThirdPersonPlayer" type="CharacterBody3D" parent="." unique_id=502573687]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16800, 80, 26400)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16800, 80, 26400)
|
||||||
script = ExtResource("2_player")
|
script = ExtResource("2_player")
|
||||||
spawn_tile_x = 31
|
spawn_tile_x = 31
|
||||||
spawn_tile_y = 49
|
spawn_tile_y = 49
|
||||||
|
movement_profile_id = "Blizzlike335"
|
||||||
|
|
||||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="ThirdPersonPlayer" unique_id=1297880621]
|
[node name="CollisionShape3D" type="CollisionShape3D" parent="ThirdPersonPlayer" unique_id=1297880621]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.05, 0)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.05, 0)
|
||||||
@@ -109,7 +115,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.05, 0)
|
|||||||
mesh = SubResource("PlayerCapsuleMesh_1")
|
mesh = SubResource("PlayerCapsuleMesh_1")
|
||||||
script = ExtResource("5_appearance")
|
script = ExtResource("5_appearance")
|
||||||
|
|
||||||
[node name="AnimationPresenter" type="Node" parent="ThirdPersonPlayer/Visual"]
|
[node name="AnimationPresenter" type="Node" parent="ThirdPersonPlayer/Visual" unique_id=2131488264]
|
||||||
script = ExtResource("6_animation")
|
script = ExtResource("6_animation")
|
||||||
|
|
||||||
[node name="CameraPivot" type="Node3D" parent="ThirdPersonPlayer" unique_id=499263249]
|
[node name="CameraPivot" type="Node3D" parent="ThirdPersonPlayer" unique_id=499263249]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
[ext_resource type="Script" path="res://src/scenes/player/third_person_camera_rig.gd" id="4_camera_rig"]
|
[ext_resource type="Script" path="res://src/scenes/player/third_person_camera_rig.gd" id="4_camera_rig"]
|
||||||
[ext_resource type="Script" path="res://src/scenes/character/character_appearance_presenter.gd" id="5_appearance"]
|
[ext_resource type="Script" path="res://src/scenes/character/character_appearance_presenter.gd" id="5_appearance"]
|
||||||
[ext_resource type="Script" path="res://src/scenes/character/character_animation_presenter.gd" id="6_animation"]
|
[ext_resource type="Script" path="res://src/scenes/character/character_animation_presenter.gd" id="6_animation"]
|
||||||
|
[ext_resource type="Script" path="res://src/render/world_render_facade.gd" id="7_render_facade"]
|
||||||
|
|
||||||
[sub_resource type="CapsuleShape3D" id="PlayerCapsuleShape_1"]
|
[sub_resource type="CapsuleShape3D" id="PlayerCapsuleShape_1"]
|
||||||
radius = 0.45
|
radius = 0.45
|
||||||
@@ -57,7 +58,6 @@ fog_depth_end = 5200.0
|
|||||||
[node name="StreamingWorld" type="Node3D" unique_id=1063159974]
|
[node name="StreamingWorld" type="Node3D" unique_id=1063159974]
|
||||||
script = ExtResource("1_sisqv")
|
script = ExtResource("1_sisqv")
|
||||||
map_name = "Kalimdor"
|
map_name = "Kalimdor"
|
||||||
streaming_focus_source_path = NodePath("ThirdPersonPlayer")
|
|
||||||
camera_path = NodePath("ThirdPersonPlayer/CameraPivot/Camera3D")
|
camera_path = NodePath("ThirdPersonPlayer/CameraPivot/Camera3D")
|
||||||
quality_preset = "High"
|
quality_preset = "High"
|
||||||
update_interval = 0.1
|
update_interval = 0.1
|
||||||
@@ -95,6 +95,11 @@ wmo_visibility_range = 3600.0
|
|||||||
debug_streaming = true
|
debug_streaming = true
|
||||||
hitch_profiler_enabled = true
|
hitch_profiler_enabled = true
|
||||||
|
|
||||||
|
[node name="WorldRenderFacade" type="Node" parent="."]
|
||||||
|
script = ExtResource("7_render_facade")
|
||||||
|
streaming_world_loader_path = NodePath("..")
|
||||||
|
streaming_focus_source_path = NodePath("../ThirdPersonPlayer")
|
||||||
|
|
||||||
[node name="ThirdPersonPlayer" type="CharacterBody3D" parent="." unique_id=502573687]
|
[node name="ThirdPersonPlayer" type="CharacterBody3D" parent="." unique_id=502573687]
|
||||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16800, 80, 26400)
|
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16800, 80, 26400)
|
||||||
script = ExtResource("2_puy8r")
|
script = ExtResource("2_puy8r")
|
||||||
|
|||||||
@@ -64,7 +64,12 @@ func _capture_async() -> void:
|
|||||||
camera.far = 50000.0
|
camera.far = 50000.0
|
||||||
camera.position = _vector3(first.get("camera", [0.0, 0.0, 0.0]))
|
camera.position = _vector3(first.get("camera", [0.0, 0.0, 0.0]))
|
||||||
(world as Node3D).add_child(camera)
|
(world as Node3D).add_child(camera)
|
||||||
world.set("streaming_focus_source_path", NodePath("CheckpointCamera"))
|
var render_facade := world.get_node_or_null("WorldRenderFacade")
|
||||||
|
if render_facade == null:
|
||||||
|
push_error("Streaming scene has no WorldRenderFacade")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
render_facade.set("streaming_focus_source_path", NodePath("../CheckpointCamera"))
|
||||||
world.set("debug_streaming", true)
|
world.set("debug_streaming", true)
|
||||||
world.set("runtime_stats_enabled", true)
|
world.set("runtime_stats_enabled", true)
|
||||||
get_root().add_child(world)
|
get_root().add_child(world)
|
||||||
@@ -126,8 +131,7 @@ func _capture_async() -> void:
|
|||||||
if player != null:
|
if player != null:
|
||||||
player.global_position = _vector3(checkpoint.get("player", checkpoint.get("target", [0.0, 0.0, 0.0])))
|
player.global_position = _vector3(checkpoint.get("player", checkpoint.get("target", [0.0, 0.0, 0.0])))
|
||||||
_set_sky_time(world, float(checkpoint.get("time_hours", 13.0)))
|
_set_sky_time(world, float(checkpoint.get("time_hours", 13.0)))
|
||||||
if world.has_method("refresh_streaming_focus"):
|
render_facade.call("refresh_streaming_focus", true)
|
||||||
world.call("refresh_streaming_focus", true)
|
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
print("RENDER_CHECKPOINT dry_run name=%s coverage=%s camera=%s target=%s yaw_offset=%.2f pitch_offset=%.2f time=%.2f" % [
|
print("RENDER_CHECKPOINT dry_run name=%s coverage=%s camera=%s target=%s yaw_offset=%.2f pitch_offset=%.2f time=%.2f" % [
|
||||||
@@ -148,7 +152,7 @@ func _capture_async() -> void:
|
|||||||
var load_started := Time.get_ticks_usec()
|
var load_started := Time.get_ticks_usec()
|
||||||
await create_timer(maxf(wait_seconds, 0.0)).timeout
|
await create_timer(maxf(wait_seconds, 0.0)).timeout
|
||||||
var load_time_ms := float(Time.get_ticks_usec() - load_started) / 1000.0
|
var load_time_ms := float(Time.get_ticks_usec() - load_started) / 1000.0
|
||||||
var metrics := await _measure_frames(maxf(measure_seconds, 0.1), world)
|
var metrics := await _measure_frames(maxf(measure_seconds, 0.1), render_facade)
|
||||||
await RenderingServer.frame_post_draw
|
await RenderingServer.frame_post_draw
|
||||||
|
|
||||||
var image := get_root().get_texture().get_image()
|
var image := get_root().get_texture().get_image()
|
||||||
@@ -189,7 +193,7 @@ func _capture_async() -> void:
|
|||||||
quit(0)
|
quit(0)
|
||||||
|
|
||||||
|
|
||||||
func _measure_frames(seconds: float, world: Node) -> Dictionary:
|
func _measure_frames(seconds: float, render_facade: Node) -> Dictionary:
|
||||||
var frame_times: Array[float] = []
|
var frame_times: Array[float] = []
|
||||||
var deadline := Time.get_ticks_usec() + int(seconds * 1000000.0)
|
var deadline := Time.get_ticks_usec() + int(seconds * 1000000.0)
|
||||||
var previous := Time.get_ticks_usec()
|
var previous := Time.get_ticks_usec()
|
||||||
@@ -200,8 +204,8 @@ func _measure_frames(seconds: float, world: Node) -> Dictionary:
|
|||||||
previous = now
|
previous = now
|
||||||
frame_times.sort()
|
frame_times.sort()
|
||||||
var snapshot := {}
|
var snapshot := {}
|
||||||
if world.has_method("render_baseline_snapshot"):
|
if render_facade.has_method("renderer_metrics_snapshot"):
|
||||||
snapshot = world.call("render_baseline_snapshot")
|
snapshot = render_facade.call("renderer_metrics_snapshot")
|
||||||
return {
|
return {
|
||||||
"frames": frame_times.size(),
|
"frames": frame_times.size(),
|
||||||
"frame_ms_p50": _percentile(frame_times, 0.50),
|
"frame_ms_p50": _percentile(frame_times, 0.50),
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b5n7erahq8i46
|
||||||
@@ -28,7 +28,12 @@ func _run_async() -> void:
|
|||||||
camera.name = "OccluderProbeCamera"
|
camera.name = "OccluderProbeCamera"
|
||||||
camera.current = true
|
camera.current = true
|
||||||
world.add_child(camera)
|
world.add_child(camera)
|
||||||
world.set("streaming_focus_source_path", NodePath(camera.name))
|
var render_facade := world.get_node_or_null("WorldRenderFacade")
|
||||||
|
if render_facade == null:
|
||||||
|
push_error("CAMERA_OCCLUDER_PROBE: streaming scene has no WorldRenderFacade")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
render_facade.set("streaming_focus_source_path", NodePath("../%s" % camera.name))
|
||||||
world.set("debug_streaming", false)
|
world.set("debug_streaming", false)
|
||||||
get_root().add_child(world)
|
get_root().add_child(world)
|
||||||
await process_frame
|
await process_frame
|
||||||
@@ -47,8 +52,7 @@ func _run_async() -> void:
|
|||||||
var camera_position := _vector3(checkpoint.get("camera", []))
|
var camera_position := _vector3(checkpoint.get("camera", []))
|
||||||
var target_position := _vector3(checkpoint.get("target", []))
|
var target_position := _vector3(checkpoint.get("target", []))
|
||||||
camera.global_position = camera_position
|
camera.global_position = camera_position
|
||||||
if world.has_method("refresh_streaming_focus"):
|
render_facade.call("refresh_streaming_focus", true)
|
||||||
world.call("refresh_streaming_focus", true)
|
|
||||||
await create_timer(maxf(0.1, wait_seconds)).timeout
|
await create_timer(maxf(0.1, wait_seconds)).timeout
|
||||||
var geometry_nodes: Array[Node3D] = []
|
var geometry_nodes: Array[Node3D] = []
|
||||||
_collect_geometry_nodes(world, geometry_nodes)
|
_collect_geometry_nodes(world, geometry_nodes)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bpdv66fxxslbc
|
||||||
@@ -37,7 +37,12 @@ func _run_async() -> void:
|
|||||||
var camera := Camera3D.new()
|
var camera := Camera3D.new()
|
||||||
camera.current = true
|
camera.current = true
|
||||||
world.add_child(camera)
|
world.add_child(camera)
|
||||||
world.set("streaming_focus_source_path", NodePath(camera.name))
|
var render_facade := world.get_node_or_null("WorldRenderFacade")
|
||||||
|
if render_facade == null:
|
||||||
|
push_error("TERRAIN_HEIGHT_PROBE: streaming scene has no WorldRenderFacade")
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
render_facade.set("streaming_focus_source_path", NodePath("../%s" % camera.name))
|
||||||
world.set("debug_streaming", false)
|
world.set("debug_streaming", false)
|
||||||
get_root().add_child(world)
|
get_root().add_child(world)
|
||||||
await process_frame
|
await process_frame
|
||||||
@@ -54,8 +59,7 @@ func _run_async() -> void:
|
|||||||
continue
|
continue
|
||||||
var camera_position := _vector3(checkpoint.get("camera", []))
|
var camera_position := _vector3(checkpoint.get("camera", []))
|
||||||
camera.global_position = camera_position
|
camera.global_position = camera_position
|
||||||
if world.has_method("refresh_streaming_focus"):
|
render_facade.call("refresh_streaming_focus", true)
|
||||||
world.call("refresh_streaming_focus", true)
|
|
||||||
await create_timer(maxf(0.1, wait_seconds)).timeout
|
await create_timer(maxf(0.1, wait_seconds)).timeout
|
||||||
var terrain_sample := _sample_terrain(world, camera_position)
|
var terrain_sample := _sample_terrain(world, camera_position)
|
||||||
terrain_sample["name"] = checkpoint.get("name", "checkpoint")
|
terrain_sample["name"] = checkpoint.get("name", "checkpoint")
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dctxeqgyptl8l
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://buw0m8pjm5h02
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://qpkd87g1w31j
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dwx3m77yybv0h
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bcg11uv05uhcr
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bopeup4jipa35
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cojh1u6igoocj
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cy5sbwxmjyue8
|
||||||
@@ -6,6 +6,7 @@ const StreamingFocusScript = preload("res://src/domain/streaming/streaming_focus
|
|||||||
const GodotWorldPositionScript = preload("res://src/domain/coordinates/godot_world_position.gd")
|
const GodotWorldPositionScript = preload("res://src/domain/coordinates/godot_world_position.gd")
|
||||||
|
|
||||||
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
|
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
|
||||||
|
const FACADE_PATH := "res://src/render/world_render_facade.gd"
|
||||||
const RUNTIME_SCENE_PATHS: Array[String] = [
|
const RUNTIME_SCENE_PATHS: Array[String] = [
|
||||||
"res://src/scenes/streaming/eastern_kingdoms_streaming.tscn",
|
"res://src/scenes/streaming/eastern_kingdoms_streaming.tscn",
|
||||||
"res://src/scenes/streaming/kalimdor_streaming.tscn",
|
"res://src/scenes/streaming/kalimdor_streaming.tscn",
|
||||||
@@ -21,6 +22,7 @@ func _initialize() -> void:
|
|||||||
var failures: Array[String] = []
|
var failures: Array[String] = []
|
||||||
_verify_scene_free_focus_value(failures)
|
_verify_scene_free_focus_value(failures)
|
||||||
_verify_loader_boundary(failures)
|
_verify_loader_boundary(failures)
|
||||||
|
_verify_facade_boundary(failures)
|
||||||
_verify_runtime_scene_wiring(failures)
|
_verify_runtime_scene_wiring(failures)
|
||||||
_verify_capture_tool_wiring(failures)
|
_verify_capture_tool_wiring(failures)
|
||||||
|
|
||||||
@@ -65,11 +67,21 @@ func _verify_loader_boundary(failures: Array[String]) -> void:
|
|||||||
_expect_true(not loader_source.contains(forbidden_text), "loader omits %s" % forbidden_text, failures)
|
_expect_true(not loader_source.contains(forbidden_text), "loader omits %s" % forbidden_text, failures)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_facade_boundary(failures: Array[String]) -> void:
|
||||||
|
var facade_source := _read_text(FACADE_PATH, failures)
|
||||||
|
for required_text in [
|
||||||
|
"class_name WorldRenderFacade",
|
||||||
|
"func set_streaming_focus(streaming_focus: StreamingFocus) -> void:",
|
||||||
|
"func refresh_streaming_focus(force: bool = false) -> bool:",
|
||||||
|
]:
|
||||||
|
_expect_true(facade_source.contains(required_text), "facade contains %s" % required_text, failures)
|
||||||
|
|
||||||
|
|
||||||
func _verify_runtime_scene_wiring(failures: Array[String]) -> void:
|
func _verify_runtime_scene_wiring(failures: Array[String]) -> void:
|
||||||
for scene_path in RUNTIME_SCENE_PATHS:
|
for scene_path in RUNTIME_SCENE_PATHS:
|
||||||
var scene_source := _read_text(scene_path, failures)
|
var scene_source := _read_text(scene_path, failures)
|
||||||
_expect_true(
|
_expect_true(
|
||||||
scene_source.contains('streaming_focus_source_path = NodePath("ThirdPersonPlayer")'),
|
scene_source.contains('streaming_focus_source_path = NodePath("../ThirdPersonPlayer")'),
|
||||||
"%s uses player focus" % scene_path,
|
"%s uses player focus" % scene_path,
|
||||||
failures
|
failures
|
||||||
)
|
)
|
||||||
@@ -78,8 +90,8 @@ func _verify_runtime_scene_wiring(failures: Array[String]) -> void:
|
|||||||
func _verify_capture_tool_wiring(failures: Array[String]) -> void:
|
func _verify_capture_tool_wiring(failures: Array[String]) -> void:
|
||||||
for tool_path in CAPTURE_TOOL_PATHS:
|
for tool_path in CAPTURE_TOOL_PATHS:
|
||||||
var tool_source := _read_text(tool_path, failures)
|
var tool_source := _read_text(tool_path, failures)
|
||||||
_expect_true(tool_source.contains('world.set("streaming_focus_source_path"'), "%s sets explicit focus source" % tool_path, failures)
|
_expect_true(tool_source.contains('render_facade.set("streaming_focus_source_path"'), "%s sets explicit focus source" % tool_path, failures)
|
||||||
_expect_true(tool_source.contains('world.call("refresh_streaming_focus", true)'), "%s uses public refresh" % tool_path, failures)
|
_expect_true(tool_source.contains('render_facade.call("refresh_streaming_focus", true)'), "%s uses facade refresh" % tool_path, failures)
|
||||||
_expect_true(not tool_source.contains('world.call("_refresh_streaming_targets_at"'), "%s avoids private refresh" % tool_path, failures)
|
_expect_true(not tool_source.contains('world.call("_refresh_streaming_targets_at"'), "%s avoids private refresh" % tool_path, failures)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://caberesg37dmj
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://c13xqbnau04i4
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
## Headless contract and wiring regression for the first M03 renderer facade seam.
|
||||||
|
|
||||||
|
const WORLD_RENDER_FACADE_SCRIPT := preload("res://src/render/world_render_facade.gd")
|
||||||
|
const GODOT_WORLD_POSITION_SCRIPT := preload("res://src/domain/coordinates/godot_world_position.gd")
|
||||||
|
const STREAMING_FOCUS_SCRIPT := preload("res://src/domain/streaming/streaming_focus.gd")
|
||||||
|
|
||||||
|
const RUNTIME_SCENE_PATHS: Array[String] = [
|
||||||
|
"res://src/scenes/streaming/eastern_kingdoms_streaming.tscn",
|
||||||
|
"res://src/scenes/streaming/kalimdor_streaming.tscn",
|
||||||
|
]
|
||||||
|
const TOOL_PATHS: Array[String] = [
|
||||||
|
"res://src/tools/capture_render_checkpoints.gd",
|
||||||
|
"res://src/tools/probe_render_camera_occluders.gd",
|
||||||
|
"res://src/tools/probe_render_terrain_height.gd",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class StreamingWorldLoaderDouble extends Node:
|
||||||
|
var current_focus: StreamingFocus
|
||||||
|
var refresh_count := 0
|
||||||
|
var metrics := {"tiles": 2, "queues": {"tile": 1}}
|
||||||
|
|
||||||
|
func set_streaming_focus(streaming_focus: StreamingFocus) -> void:
|
||||||
|
current_focus = streaming_focus
|
||||||
|
|
||||||
|
func refresh_streaming_focus(_force: bool = false) -> bool:
|
||||||
|
refresh_count += 1
|
||||||
|
return current_focus != null
|
||||||
|
|
||||||
|
func render_baseline_snapshot() -> Dictionary:
|
||||||
|
return metrics
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
var failures: Array[String] = []
|
||||||
|
await _verify_delegation_and_snapshot_isolation(failures)
|
||||||
|
_verify_repository_wiring(failures)
|
||||||
|
|
||||||
|
if not failures.is_empty():
|
||||||
|
for failure in failures:
|
||||||
|
push_error("WORLD_RENDER_FACADE: %s" % failure)
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
print("WORLD_RENDER_FACADE PASS delegation=3 runtime_scenes=2 tools=3")
|
||||||
|
quit(0)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_delegation_and_snapshot_isolation(failures: Array[String]) -> void:
|
||||||
|
var test_root := Node.new()
|
||||||
|
var loader := StreamingWorldLoaderDouble.new()
|
||||||
|
loader.name = "StreamingWorldLoaderDouble"
|
||||||
|
var source := Node3D.new()
|
||||||
|
source.name = "FocusSource"
|
||||||
|
source.position = Vector3(10.0, 20.0, 30.0)
|
||||||
|
var facade = WORLD_RENDER_FACADE_SCRIPT.new()
|
||||||
|
facade.name = "WorldRenderFacade"
|
||||||
|
facade.streaming_world_loader_path = NodePath("../StreamingWorldLoaderDouble")
|
||||||
|
facade.streaming_focus_source_path = NodePath("../FocusSource")
|
||||||
|
test_root.add_child(loader)
|
||||||
|
test_root.add_child(source)
|
||||||
|
test_root.add_child(facade)
|
||||||
|
get_root().add_child(test_root)
|
||||||
|
await process_frame
|
||||||
|
|
||||||
|
_expect_true(loader.current_focus != null, "ready captures explicit source", failures)
|
||||||
|
if loader.current_focus != null:
|
||||||
|
_expect_near(loader.current_focus.world_position.x_units, 10.0, "source X", failures)
|
||||||
|
_expect_near(loader.current_focus.world_position.y_units, 20.0, "source Y", failures)
|
||||||
|
_expect_near(loader.current_focus.world_position.z_units, 30.0, "source Z", failures)
|
||||||
|
|
||||||
|
var typed_focus = STREAMING_FOCUS_SCRIPT.new(GODOT_WORLD_POSITION_SCRIPT.new(40.0, 50.0, 60.0))
|
||||||
|
facade.streaming_focus_source_path = NodePath()
|
||||||
|
facade.set_streaming_focus(typed_focus)
|
||||||
|
_expect_true(facade.refresh_streaming_focus(true), "typed focus refresh delegates", failures)
|
||||||
|
_expect_true(loader.current_focus == typed_focus, "typed focus reference delegates", failures)
|
||||||
|
|
||||||
|
var snapshot: Dictionary = facade.renderer_metrics_snapshot()
|
||||||
|
snapshot["tiles"] = 999
|
||||||
|
(snapshot["queues"] as Dictionary)["tile"] = 999
|
||||||
|
_expect_true(int(loader.metrics.tiles) == 2, "top-level metrics are detached", failures)
|
||||||
|
_expect_true(int(loader.metrics.queues.tile) == 1, "nested metrics are detached", failures)
|
||||||
|
test_root.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_repository_wiring(failures: Array[String]) -> void:
|
||||||
|
for scene_path in RUNTIME_SCENE_PATHS:
|
||||||
|
var scene_source := _read_text(scene_path, failures)
|
||||||
|
_expect_true(scene_source.contains('path="res://src/render/world_render_facade.gd"'), "%s loads facade" % scene_path, failures)
|
||||||
|
_expect_true(scene_source.contains('[node name="WorldRenderFacade" type="Node" parent="."]'), "%s owns facade node" % scene_path, failures)
|
||||||
|
_expect_true(scene_source.contains('streaming_focus_source_path = NodePath("../ThirdPersonPlayer")'), "%s facade uses player focus" % scene_path, failures)
|
||||||
|
|
||||||
|
for tool_path in TOOL_PATHS:
|
||||||
|
var tool_source := _read_text(tool_path, failures)
|
||||||
|
_expect_true(tool_source.contains('get_node_or_null("WorldRenderFacade")'), "%s resolves facade" % tool_path, failures)
|
||||||
|
_expect_true(not tool_source.contains('world.call("refresh_streaming_focus"'), "%s avoids direct streamer refresh" % tool_path, failures)
|
||||||
|
_expect_true(not tool_source.contains('world.set("streaming_focus_source_path"'), "%s avoids direct streamer focus config" % tool_path, failures)
|
||||||
|
|
||||||
|
|
||||||
|
func _read_text(path: String, failures: Array[String]) -> String:
|
||||||
|
var file := FileAccess.open(path, FileAccess.READ)
|
||||||
|
if file == null:
|
||||||
|
failures.append("cannot open %s" % path)
|
||||||
|
return ""
|
||||||
|
return file.get_as_text()
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_near(actual_value: float, expected_value: float, label: String, failures: Array[String]) -> void:
|
||||||
|
if not is_equal_approx(actual_value, expected_value):
|
||||||
|
failures.append("%s expected %.3f, got %.3f" % [label, expected_value, actual_value])
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
|
||||||
|
if not actual_value:
|
||||||
|
failures.append("%s expected true" % label)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://baoiewiesd02u
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# M02 — Player Decomposition
|
# M02 — Player Decomposition
|
||||||
|
|
||||||
<!-- OPENWC_TARGET:ACTIVE -->
|
<!-- OPENWC_TARGET:DONE -->
|
||||||
|
<!-- OPENWC_TARGET_DONE:M02:work/sindo-main-codex/m02-integrator-closeout:2026-07-15 -->
|
||||||
|
|
||||||
## Outcome
|
## Outcome
|
||||||
|
|
||||||
@@ -8,14 +9,14 @@
|
|||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
- [ ] Заменить hardcoded keys на remappable Input Map actions с профилем 3.3.5a.
|
- [x] Заменить hardcoded keys на remappable Input Map actions с профилем 3.3.5a.
|
||||||
- [ ] Выделить `PlayerInputSource → MoveIntent`.
|
- [x] Выделить `PlayerInputSource → MoveIntent`.
|
||||||
- [ ] Выделить local movement state/controller.
|
- [x] Выделить local movement state/controller.
|
||||||
- [ ] Выделить `TerrainQuery` и убрать прямой ADT parsing из player.
|
- [x] Выделить `TerrainQuery` и убрать прямой ADT parsing из player.
|
||||||
- [ ] Выделить third-person camera rig и camera collision policy.
|
- [x] Выделить third-person camera rig и camera collision policy.
|
||||||
- [ ] Выделить character appearance и animation presenters.
|
- [x] Выделить character appearance и animation presenters.
|
||||||
- [ ] Оставить sprint/flight debug-функции только в sandbox profile.
|
- [x] Оставить sprint/flight debug-функции только в sandbox profile.
|
||||||
- [ ] Добавить movement/camera state tests и regression scene.
|
- [x] Добавить movement/camera state tests и regression scene.
|
||||||
|
|
||||||
## Fidelity evidence
|
## Fidelity evidence
|
||||||
|
|
||||||
@@ -27,10 +28,47 @@
|
|||||||
|
|
||||||
## Evidence
|
## Evidence
|
||||||
|
|
||||||
- Date:
|
- Date: 2026-07-15
|
||||||
- Revision/worktree:
|
- Revision/worktree: merged master `eb122d5`; closeout
|
||||||
- Commands:
|
`work/sindo-main-codex/m02-integrator-closeout`.
|
||||||
- Results:
|
- Commands: `verify_player_input.gd`; `verify_local_player_movement.gd`;
|
||||||
- Fidelity comparison:
|
`verify_player_movement_capabilities.gd`; `verify_terrain_query.gd`;
|
||||||
- Changed files:
|
`verify_third_person_camera_rig.gd`; `verify_character_presentation.gd`;
|
||||||
- Remaining risks:
|
`verify_coordinate_conversion_boundaries.gd`; `verify_streaming_focus.gd`;
|
||||||
|
`tools/run_render_baseline.ps1 -DryRun`; documentation, coordination and
|
||||||
|
diff-hygiene gates.
|
||||||
|
- Results: input PASS (`16` actions, `9` intent cases, `3` profiles, one
|
||||||
|
provenance fixture, `3` real-scene cases); movement PASS (`15` cases, `2`
|
||||||
|
state transitions); profile capabilities PASS (`3` profiles, sprint/flight
|
||||||
|
allow/reject and `2` scenes); terrain PASS (`4` contract, interpolation,
|
||||||
|
cache and `2` failure cases); camera PASS (`12` state and `3` policy cases);
|
||||||
|
presentation PASS (`9` appearance and `10` animation cases across `3`
|
||||||
|
scenes); coordinate boundary PASS (`103` files, `5` consumers); StreamingFocus
|
||||||
|
PASS (`2` runtime scenes, `3` capture tools). Renderer dry-run passed project,
|
||||||
|
material, dedupe and shutdown gates, manifest `7/7/7`, calibration maximum
|
||||||
|
error `0.000015`, server-spawn renderer and all seven planned checkpoints.
|
||||||
|
- Fidelity comparison: selected build-12340 default bindings are direct private
|
||||||
|
observations pinned by `DefaultBindings.wtf` SHA-256
|
||||||
|
`35FFA0E4E2356A13A0633A848F9E57ABE7DF9D8A16C2245E15D33013AD2F56A5`
|
||||||
|
and `Bindings.xml` SHA-256
|
||||||
|
`2E01276AFB7462F1417710F13B4BF1A456341F7F28CC8AE0D6D9C7442D7F6793`;
|
||||||
|
only sanitized selected facts are committed. `3.141594` rad/s keyboard turn
|
||||||
|
is pinned to TrinityCore 3.3.5 revision
|
||||||
|
`2853a621d6af91a803787a2b8a509f4ce3e0300d`; right-mouse A/D strafe routing
|
||||||
|
is supported by WoWee revision `626243e937fb93965fa583a6507ed5a1aa7dda4b`.
|
||||||
|
These two sources are compatibility references, not direct client timing/
|
||||||
|
conditional-behavior proof. Translation speeds and camera values remain
|
||||||
|
sandbox regression baselines rather than original-client parity evidence;
|
||||||
|
jump/fall/swim/fly gameplay is not implemented by this decomposition target.
|
||||||
|
Debug sprint/free flight is proven unable to execute in `Blizzlike335`.
|
||||||
|
- Changed files: typed input intent/actions/source, local movement/capabilities,
|
||||||
|
terrain query, camera rig/collision policy, character appearance/animation
|
||||||
|
presenters, player composition, Input Map configuration, asset-free regression
|
||||||
|
scenes, sanitized input fixture, M02 verifiers, module specifications and M02
|
||||||
|
coordination claims.
|
||||||
|
- Remaining risks: direct build-12340 timing evidence for movement/camera,
|
||||||
|
jump/fall/swim/fly, autorun, left-button camera/select behavior, active camera
|
||||||
|
collision, terrain slopes/holes/liquids, persisted keybindings, server authority,
|
||||||
|
prediction and reconciliation remain for M09/M10/M12. Runtime renderer scenes
|
||||||
|
continue to default to explicit `RenderSandbox` until an application shell
|
||||||
|
selects `Blizzlike335`. M02 makes no complete `1:1` compatibility claim.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# M03 — Renderer Facade and Safe Extraction
|
# M03 — Renderer Facade and Safe Extraction
|
||||||
|
|
||||||
<!-- OPENWC_TARGET:OPEN -->
|
<!-- OPENWC_TARGET:ACTIVE -->
|
||||||
|
|
||||||
## Outcome
|
## Outcome
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ OpenWC должен предоставить четыре согласованн
|
|||||||
|
|
||||||
- MPQ/BLP/ADT/WDT/M2/WMO native parsing и renderer vertical slice уже существуют.
|
- MPQ/BLP/ADT/WDT/M2/WMO native parsing и renderer vertical slice уже существуют.
|
||||||
- Runtime streaming, terrain quality, MultiMesh M2, WMO caches, liquids, sky и character experiments находятся в рабочем состоянии, но сосредоточены в крупных orchestration scripts.
|
- Runtime streaming, terrain quality, MultiMesh M2, WMO caches, liquids, sky и character experiments находятся в рабочем состоянии, но сосредоточены в крупных orchestration scripts.
|
||||||
- Renderer baseline M00 завершён: paired comparison с оригинальным клиентом записывает измеренные gaps без заявления parity, а обязательные diagnostic gaps закрыты или явно классифицированы. M01 завершил coordinate, identity и streaming-focus seams; текущая работа M02 декомпозирует player input, movement, terrain query, camera и presentation без observable regression.
|
- Renderer baseline M00 завершён: paired comparison с оригинальным клиентом записывает измеренные gaps без заявления parity, а обязательные diagnostic gaps закрыты или явно классифицированы. M01 завершил coordinate, identity и streaming-focus seams; M02 декомпозировал player input, movement, terrain query, camera и presentation без observable regression. Текущая работа M03 закрывает renderer стабильным facade и извлекает planner/scheduler без rewrite.
|
||||||
- Gameplay domain, network protocol, production UI/Lua, audio orchestration и server adapters в основном предстоит реализовать.
|
- Gameplay domain, network protocol, production UI/Lua, audio orchestration и server adapters в основном предстоит реализовать.
|
||||||
- Editor plugin пока решает extraction/preview задачи, но не является полноценной authoring platform.
|
- Editor plugin пока решает extraction/preview задачи, но не является полноценной authoring platform.
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
## Current target
|
## Current target
|
||||||
|
|
||||||
`M02` — [02-player-decomposition.md](02-player-decomposition.md)
|
`M03` — [03-renderer-facade.md](03-renderer-facade.md)
|
||||||
|
|
||||||
Одновременно `ACTIVE` может быть только одна цель. Следующая цель становится `ACTIVE` после появления валидной `OPENWC_TARGET_DONE` метки у предыдущей.
|
Одновременно `ACTIVE` может быть только одна цель. Следующая цель становится `ACTIVE` после появления валидной `OPENWC_TARGET_DONE` метки у предыдущей.
|
||||||
|
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| M00 | [Renderer baseline](00-render-baseline.md) | — | DONE |
|
| M00 | [Renderer baseline](00-render-baseline.md) | — | DONE |
|
||||||
| M01 | [Coordinates and architecture seams](01-coordinates-and-seams.md) | M00 | DONE |
|
| M01 | [Coordinates and architecture seams](01-coordinates-and-seams.md) | M00 | DONE |
|
||||||
| M02 | [Player decomposition](02-player-decomposition.md) | M01 | ACTIVE |
|
| M02 | [Player decomposition](02-player-decomposition.md) | M01 | DONE |
|
||||||
| M03 | [Renderer facade and extraction](03-renderer-facade.md) | M02 | OPEN |
|
| M03 | [Renderer facade and extraction](03-renderer-facade.md) | M02 | ACTIVE |
|
||||||
| M04 | [Godot Editor shell](04-editor-shell.md) | M03 | OPEN |
|
| M04 | [Godot Editor shell](04-editor-shell.md) | M03 | OPEN |
|
||||||
| M05 | [Content Project](05-content-project.md) | M04 | OPEN |
|
| M05 | [Content Project](05-content-project.md) | M04 | OPEN |
|
||||||
| M06 | [Server database adapters](06-server-adapters.md) | M05 | OPEN |
|
| M06 | [Server database adapters](06-server-adapters.md) | M05 | OPEN |
|
||||||
|
|||||||
Reference in New Issue
Block a user