render: extract WMO runtime Mesh finalizer
This commit is contained in:
@@ -1449,6 +1449,22 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
|
|||||||
- ADT parsing, quality tasks/results, tile state, cache format versions, material/
|
- ADT parsing, quality tasks/results, tile state, cache format versions, material/
|
||||||
Node/RID finalization, budgets and visible terrain behavior remain loader-owned.
|
Node/RID finalization, budgets and visible terrain behavior remain loader-owned.
|
||||||
|
|
||||||
|
## 2026-07-18 WMO Runtime Mesh Finalizer Extraction
|
||||||
|
|
||||||
|
- `WmoRuntimeMeshFinalizer` now owns cached WMO Mesh material refresh version
|
||||||
|
`10`, in-place ArrayMesh surface iteration and WMOBuilder material-definition
|
||||||
|
reconstruction.
|
||||||
|
- `StreamingWorldLoader` retains cached-scene traversal, lightweight build-job
|
||||||
|
traversal, Node/MultiMesh attachment, permits and lifetime, and delegates each
|
||||||
|
borrowed Mesh to the service.
|
||||||
|
- The historical metadata keys, compact texture0/texture1/texture2 ordering,
|
||||||
|
flags/shader/blend values, cached shader colors and exact Mesh identity are
|
||||||
|
unchanged. Null/unmarked surfaces and null builder results retain their prior
|
||||||
|
Material.
|
||||||
|
- Asset-free verification covers `27` identity/version/type/material/source
|
||||||
|
cases and 1,000 current-Mesh calls. This is orchestration extraction, not new
|
||||||
|
build-12340 material or visual parity evidence.
|
||||||
|
|
||||||
## 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.
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
| WMO render Resource finalizer | Implemented extraction | [`wmo-render-resource-finalizer.md`](wmo-render-resource-finalizer.md) |
|
| WMO render Resource finalizer | Implemented extraction | [`wmo-render-resource-finalizer.md`](wmo-render-resource-finalizer.md) |
|
||||||
| WMO scene Resource cache state | Implemented extraction | [`wmo-scene-resource-cache-state.md`](wmo-scene-resource-cache-state.md) |
|
| WMO scene Resource cache state | Implemented extraction | [`wmo-scene-resource-cache-state.md`](wmo-scene-resource-cache-state.md) |
|
||||||
| WMO scene Resource finalizer | Implemented extraction | [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md) |
|
| WMO scene Resource finalizer | Implemented extraction | [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md) |
|
||||||
|
| WMO runtime Mesh finalizer | Implemented extraction | [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-finalizer.md) |
|
||||||
| Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) |
|
| Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) |
|
||||||
| Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) |
|
| Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) |
|
||||||
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
|
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
# WMO Runtime Mesh Finalizer
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Status | Implemented extraction |
|
||||||
|
| Target/work package | M03 / `M03-RND-WMO-RUNTIME-MESH-FINALIZER-001` |
|
||||||
|
| Owners | Cached WMO runtime Mesh refresh version, surface iteration and material reconstruction |
|
||||||
|
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-runtime-mesh-finalizer`, 2026-07-18 |
|
||||||
|
| Profiles/capabilities | Profile-independent cached WMO material refresh |
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Finalize stale cached WMO runtime Mesh materials outside
|
||||||
|
`StreamingWorldLoader`: preserve exact Mesh identity, stamp the historical
|
||||||
|
refresh version and reconstruct eligible surface materials through WMOBuilder.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Traverse scene Nodes or own Mesh/MultiMesh attachment and lifetime.
|
||||||
|
- Select WMO cache paths, poll ResourceLoader or schedule render build jobs.
|
||||||
|
- Change WMOBuilder shaders, textures, blend rules or cache serialization.
|
||||||
|
- Share an abstraction with the behaviorally different M2 finalizer.
|
||||||
|
|
||||||
|
## Context and boundaries
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Loader[StreamingWorldLoader traversal/build step] -->|Mesh plus extracted directory| Finalizer[WmoRuntimeMeshFinalizer]
|
||||||
|
Finalizer -->|material definition plus compact texture paths| Builder[WMOBuilder material boundary]
|
||||||
|
Builder -->|rebuilt Material| Finalizer
|
||||||
|
Finalizer -->|same Mesh identity| Loader
|
||||||
|
```
|
||||||
|
|
||||||
|
The loader owns scene traversal and composition. The finalizer owns only the
|
||||||
|
in-place Resource operation and depends on an injected WMO material builder.
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `finalize_mesh(mesh, extracted_directory)` | Command/query | Stamp and refresh a stale Mesh in place | Renderer main thread; stateless between calls | Null returns null; unsupported Mesh returns exact identity |
|
||||||
|
| `rebuild_cached_material(material, extracted_directory)` | Query/boundary | Reconstruct one metadata-bearing cached WMO Material | Renderer main thread; borrowed input | Null/unmarked/missing builder returns null |
|
||||||
|
|
||||||
|
## Inputs and outputs
|
||||||
|
|
||||||
|
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Input | `Mesh` and refresh metadata | Cached WMO scene/render group | Finalizer | Borrowed Resource | Call duration; identity retained |
|
||||||
|
| Input | Extracted content directory | Loader configuration | WMOBuilder | Borrowed String value | Call duration |
|
||||||
|
| Input | Texture paths, WMO flags/shader/blend and cached colors | Surface Material metadata/parameters | Finalizer | Borrowed values | One surface rebuild |
|
||||||
|
| Output | Compact texture-path array and material definition | Finalizer | WMOBuilder | Detached values | One builder call |
|
||||||
|
| Output | Rebuilt Material | WMOBuilder | ArrayMesh surface | Surface adopts exact Resource | Mesh lifetime |
|
||||||
|
| Output | Exact input Mesh | Finalizer | Loader traversal/build step | Caller retains ownership | Existing cache/scene lifetime |
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Mesh[Borrowed Mesh] --> Null{Null?}
|
||||||
|
Null -->|yes| ReturnNull[Return null]
|
||||||
|
Null -->|no| Current{Refresh version at least 10?}
|
||||||
|
Current -->|yes| ReturnSame[Return exact Mesh]
|
||||||
|
Current -->|no| Stamp[Stamp version 10]
|
||||||
|
Stamp --> Array{ArrayMesh?}
|
||||||
|
Array -->|no| ReturnSame
|
||||||
|
Array -->|yes| Surface[Iterate surfaces]
|
||||||
|
Surface --> Eligible{Material has texture0 metadata?}
|
||||||
|
Eligible -->|no| Keep[Keep exact Material]
|
||||||
|
Eligible -->|yes| Definition[Compact paths and copy metadata/colors]
|
||||||
|
Definition --> Builder[WMOBuilder build material]
|
||||||
|
Builder --> Result{Non-null result?}
|
||||||
|
Result -->|yes| Adopt[Replace surface Material]
|
||||||
|
Result -->|no| Keep
|
||||||
|
Keep --> Surface
|
||||||
|
Adopt --> Surface
|
||||||
|
Surface -->|done| ReturnSame
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lifecycle/state
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> Stale: missing/version below 10
|
||||||
|
[*] --> Current: version at least 10
|
||||||
|
Stale --> Current: stamp before optional surface refresh
|
||||||
|
Current --> Current: subsequent calls are identity-only
|
||||||
|
```
|
||||||
|
|
||||||
|
The version stamp is intentionally applied before type/material eligibility, as
|
||||||
|
in the extracted loader behavior. The finalizer retains no Mesh or Material.
|
||||||
|
|
||||||
|
## Main sequence
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Loader as StreamingWorldLoader
|
||||||
|
participant Finalizer as WmoRuntimeMeshFinalizer
|
||||||
|
participant Mesh as ArrayMesh
|
||||||
|
participant Builder as WMOBuilder
|
||||||
|
Loader->>Finalizer: finalize_mesh(mesh, extracted_directory)
|
||||||
|
Finalizer->>Mesh: read/stamp refresh metadata
|
||||||
|
loop each stale ArrayMesh surface
|
||||||
|
Finalizer->>Mesh: surface_get_material(index)
|
||||||
|
alt cached WMO metadata exists
|
||||||
|
Finalizer->>Builder: _build_material(definition, compact paths, directory)
|
||||||
|
Builder-->>Finalizer: rebuilt Material or null
|
||||||
|
Finalizer->>Mesh: surface_set_material when non-null
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Finalizer-->>Loader: exact Mesh identity
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependency diagram
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
Loader[StreamingWorldLoader] --> Finalizer[WmoRuntimeMeshFinalizer]
|
||||||
|
Finalizer --> Engine[Mesh / ArrayMesh / Material]
|
||||||
|
Finalizer --> Builder[Injected WMO material builder]
|
||||||
|
Finalizer -. no dependency .-> Nodes[Node traversal/lifetime]
|
||||||
|
Finalizer -. no dependency .-> RL[ResourceLoader]
|
||||||
|
Finalizer -. no dependency .-> Queue[Build queue/scheduler]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ownership, threading and resources
|
||||||
|
|
||||||
|
- The renderer main thread serializes Mesh metadata and surface mutation.
|
||||||
|
- The caller owns Mesh identity, scene traversal, attachment and destruction.
|
||||||
|
- ArrayMesh surfaces adopt only non-null builder results; otherwise the exact
|
||||||
|
cached Material remains attached.
|
||||||
|
- The service retains no Resource, Node, RID, file, queue or per-path state.
|
||||||
|
|
||||||
|
## Errors, cancellation and recovery
|
||||||
|
|
||||||
|
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| Null Mesh | Guard | Return null | Synthetic contract | Correct caller input |
|
||||||
|
| Current Mesh | Version metadata | Return exact identity, no builder call | Identity/version contract | Version bump makes it stale |
|
||||||
|
| Unsupported Mesh subtype | Type check after stamp | Return exact identity | PrimitiveMesh contract | No surface work required |
|
||||||
|
| Null/unmarked Material | Metadata guard | Preserve exact surface value | Surface contract | Rebuild cache with metadata |
|
||||||
|
| Missing builder/null result | Boundary guard/result | Preserve exact Material | Synthetic contract | Correct composition/retry after version bump |
|
||||||
|
| Shutdown | Loader lifecycle | No retained work to cancel | Shutdown suite | New loader composes a new service |
|
||||||
|
|
||||||
|
## Configuration and capabilities
|
||||||
|
|
||||||
|
Refresh version `10` and metadata key `wow_wmo_material_refresh_version` are the
|
||||||
|
existing runtime compatibility boundary. No project setting, profile, permit or
|
||||||
|
feature flag is introduced.
|
||||||
|
|
||||||
|
## Persistence, cache and migration
|
||||||
|
|
||||||
|
The module writes only the existing runtime Mesh metadata stamp and serializes
|
||||||
|
nothing. It does not change WMO scene/render cache formats or trigger rebakes.
|
||||||
|
A future material-rule change must deliberately bump the refresh version.
|
||||||
|
|
||||||
|
## Diagnostics and observability
|
||||||
|
|
||||||
|
The finalizer emits no logs and allocates no metrics. Existing `wmobuild` queue
|
||||||
|
metrics and loader diagnostics remain the operational correlation surface.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `verify_wmo_runtime_mesh_finalizer.gd`: null/current/non-ArrayMesh identity,
|
||||||
|
version stamp, null/unmarked surface retention, compact path indices,
|
||||||
|
flags/shader/blend/default and shader colors, exact builder adoption,
|
||||||
|
missing-builder behavior, source ownership and 1,000 current calls under one second.
|
||||||
|
- Adjacent WMO queue/cache/finalizer, shutdown, material and baseline regressions
|
||||||
|
protect orchestration and visible output.
|
||||||
|
- Fidelity evidence is exact behavior-preserving extraction; no private asset or
|
||||||
|
original-client visual-parity claim is added.
|
||||||
|
|
||||||
|
## Extension points
|
||||||
|
|
||||||
|
- Asset-backed WMO fixtures may compare reconstructed surface parameters without
|
||||||
|
changing the service contract.
|
||||||
|
- A deliberate material refresh change may bump the owned version with old/new
|
||||||
|
fixtures and paired visual evidence.
|
||||||
|
|
||||||
|
## Capability status
|
||||||
|
|
||||||
|
| Capability | Status | Evidence | Gap/next step |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Runtime refresh admission | Implemented extraction | Identity/version/type contracts | Serialized cache fixture pending |
|
||||||
|
| Cached material definition reconstruction | Implemented extraction | Path/metadata/color contracts | Asset-backed visual comparison pending |
|
||||||
|
| Scene traversal/materialization | Loader-owned | Existing WMO regressions | Further safe extraction pending |
|
||||||
|
|
||||||
|
## Known gaps and risks
|
||||||
|
|
||||||
|
- Surface mutation and WMOBuilder material construction remain synchronous
|
||||||
|
main-thread work.
|
||||||
|
- No proprietary WMO corpus, long leak run, GPU timing, traversal p95/p99 or
|
||||||
|
paired build-12340 visual capture is included.
|
||||||
|
|
||||||
|
## Source map
|
||||||
|
|
||||||
|
| Path | Responsibility |
|
||||||
|
|---|---|
|
||||||
|
| `src/render/wmo/wmo_runtime_mesh_finalizer.gd` | Refresh admission, surface iteration and material definition reconstruction |
|
||||||
|
| `addons/mpq_extractor/loaders/wmo_builder.gd` | WMO shader/material construction semantics |
|
||||||
|
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, Node traversal, build jobs and lifetime |
|
||||||
|
| `src/tools/verify_wmo_runtime_mesh_finalizer.gd` | Identity/version/material/source/timing regression |
|
||||||
|
|
||||||
|
## Related decisions and references
|
||||||
|
|
||||||
|
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
|
||||||
|
- [`wmo-render-resource-finalizer.md`](wmo-render-resource-finalizer.md)
|
||||||
|
- [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md)
|
||||||
|
- [`world-renderer.md`](world-renderer.md)
|
||||||
|
- [`../../RENDER.md`](../../RENDER.md)
|
||||||
|
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
|
||||||
@@ -622,6 +622,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
| WMO render Resource finalizer | Implemented extraction | Status/order/script/format/adoption/source/timing contract | Serialized/asset-backed corrupt-cache and leak evidence pending |
|
| WMO render Resource finalizer | Implemented extraction | Status/order/script/format/adoption/source/timing contract | Serialized/asset-backed corrupt-cache and leak evidence pending |
|
||||||
| WMO scene Resource cache state | Implemented extraction | Scene-free lifecycle/direct-missing/source/timing plus shutdown contract | Asset-backed traversal/leak evidence pending |
|
| WMO scene Resource cache state | Implemented extraction | Scene-free lifecycle/direct-missing/source/timing plus shutdown contract | Asset-backed traversal/leak evidence pending |
|
||||||
| WMO scene Resource finalizer | Implemented extraction | Status/order/type/probe/lifetime/adoption/source/timing contract | Serialized stale/oversize and asset-backed evidence pending |
|
| WMO scene Resource finalizer | Implemented extraction | Status/order/type/probe/lifetime/adoption/source/timing contract | Serialized stale/oversize and asset-backed evidence pending |
|
||||||
|
| WMO runtime Mesh finalizer | Implemented extraction | Identity/version/material-definition/source/timing contract | Asset-backed visual/leak/GPU/p95/p99 evidence pending |
|
||||||
| WMO rendering | Partial | Cached group rendering | Portals/rooms/material parity |
|
| WMO rendering | Partial | Cached group rendering | Portals/rooms/material parity |
|
||||||
| ADT water load pipeline state | Implemented extraction | Scene-free FIFO/task/thread/source/timing contract | Parse/finalization and asset-backed traversal/leak evidence pending |
|
| ADT water load pipeline state | Implemented extraction | Scene-free FIFO/task/thread/source/timing contract | Parse/finalization and asset-backed traversal/leak evidence pending |
|
||||||
| Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity |
|
| Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity |
|
||||||
@@ -698,6 +699,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
| `src/render/m2/m2_prototype_cache_state.gd` | Detached prototype ownership and negative lookup outcomes |
|
| `src/render/m2/m2_prototype_cache_state.gd` | Detached prototype ownership and negative lookup outcomes |
|
||||||
| `src/render/wmo/wmo_render_resource_finalizer.gd` | Lightweight WMO terminal polling, validation and publication |
|
| `src/render/wmo/wmo_render_resource_finalizer.gd` | Lightweight WMO terminal polling, validation and publication |
|
||||||
| `src/render/wmo/wmo_scene_resource_finalizer.gd` | Cached WMO terminal polling, probe validation/lifetime and publication |
|
| `src/render/wmo/wmo_scene_resource_finalizer.gd` | Cached WMO terminal polling, probe validation/lifetime and publication |
|
||||||
|
| `src/render/wmo/wmo_runtime_mesh_finalizer.gd` | Cached WMO runtime refresh admission, surface iteration and material reconstruction |
|
||||||
| `src/render/streaming/streaming_target_planner.gd` | Scene-free wanted/retained ADT target calculation |
|
| `src/render/streaming/streaming_target_planner.gd` | Scene-free wanted/retained ADT target calculation |
|
||||||
| `src/render/streaming/streaming_target_policy.gd` | Immutable renderer radius/prefetch policy |
|
| `src/render/streaming/streaming_target_policy.gd` | Immutable renderer radius/prefetch policy |
|
||||||
| `src/render/streaming/streaming_target_plan.gd` | Immutable planner result with read-only tile-key sets |
|
| `src/render/streaming/streaming_target_plan.gd` | Immutable planner result with read-only tile-key sets |
|
||||||
@@ -734,6 +736,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
|||||||
| `src/tools/verify_m2_prototype_cache_state.gd` | M2 prototype identity/negative/lifecycle/boundary/timing regression |
|
| `src/tools/verify_m2_prototype_cache_state.gd` | M2 prototype identity/negative/lifecycle/boundary/timing regression |
|
||||||
| `src/tools/verify_wmo_render_resource_finalizer.gd` | WMO render status/order/validation/adoption/boundary/timing regression |
|
| `src/tools/verify_wmo_render_resource_finalizer.gd` | WMO render status/order/validation/adoption/boundary/timing regression |
|
||||||
| `src/tools/verify_wmo_scene_resource_finalizer.gd` | WMO scene status/order/probe/lifetime/adoption/boundary/timing regression |
|
| `src/tools/verify_wmo_scene_resource_finalizer.gd` | WMO scene status/order/probe/lifetime/adoption/boundary/timing regression |
|
||||||
|
| `src/tools/verify_wmo_runtime_mesh_finalizer.gd` | WMO Mesh identity/version/material-definition/boundary/timing regression |
|
||||||
| `src/tools/verify_streaming_target_planner.gd` | Planner behavior, dependency and bounded timing regression |
|
| `src/tools/verify_streaming_target_planner.gd` | Planner behavior, dependency and bounded timing regression |
|
||||||
| `src/tools/verify_render_budget_scheduler.gd` | Scheduler bounds, shared-lane priority, cancellation and timing regression |
|
| `src/tools/verify_render_budget_scheduler.gd` | Scheduler bounds, shared-lane priority, cancellation and timing regression |
|
||||||
| `src/tools/verify_renderer_internal_access.gd` | Gameplay/EditorPlugin/registered renderer-tool boundary gate derived from private streamer fields |
|
| `src/tools/verify_renderer_internal_access.gd` | Gameplay/EditorPlugin/registered renderer-tool boundary gate derived from private streamer fields |
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
class_name WmoRuntimeMeshFinalizer
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
## Refreshes cached WMO runtime Mesh materials through the configured builder.
|
||||||
|
## Scene traversal, placement and Node lifetime remain caller-owned.
|
||||||
|
|
||||||
|
const MATERIAL_REFRESH_VERSION := 10
|
||||||
|
const MATERIAL_REFRESH_VERSION_METADATA := "wow_wmo_material_refresh_version"
|
||||||
|
|
||||||
|
var _wmo_material_builder: Object
|
||||||
|
|
||||||
|
|
||||||
|
func _init(wmo_material_builder: Object) -> void:
|
||||||
|
_wmo_material_builder = wmo_material_builder
|
||||||
|
|
||||||
|
|
||||||
|
## Finalizes a stale Mesh in place and returns the exact input Resource identity.
|
||||||
|
## Null remains null; already-current meshes do not cross the builder boundary.
|
||||||
|
func finalize_mesh(mesh: Mesh, extracted_directory: String) -> Mesh:
|
||||||
|
if mesh == null:
|
||||||
|
return null
|
||||||
|
if int(mesh.get_meta(MATERIAL_REFRESH_VERSION_METADATA, 0)) >= MATERIAL_REFRESH_VERSION:
|
||||||
|
return mesh
|
||||||
|
mesh.set_meta(MATERIAL_REFRESH_VERSION_METADATA, MATERIAL_REFRESH_VERSION)
|
||||||
|
if not (mesh is ArrayMesh):
|
||||||
|
return mesh
|
||||||
|
|
||||||
|
var array_mesh := mesh as ArrayMesh
|
||||||
|
for surface_index in range(array_mesh.get_surface_count()):
|
||||||
|
var rebuilt_material := rebuild_cached_material(
|
||||||
|
array_mesh.surface_get_material(surface_index),
|
||||||
|
extracted_directory
|
||||||
|
)
|
||||||
|
if rebuilt_material != null:
|
||||||
|
array_mesh.surface_set_material(surface_index, rebuilt_material)
|
||||||
|
return mesh
|
||||||
|
|
||||||
|
|
||||||
|
## Rebuilds a material carrying the WMO cache metadata contract. Materials
|
||||||
|
## without texture0 metadata and failed builder results remain unchanged by the caller.
|
||||||
|
func rebuild_cached_material(
|
||||||
|
material: Material,
|
||||||
|
extracted_directory: String
|
||||||
|
) -> Material:
|
||||||
|
if (
|
||||||
|
material == null
|
||||||
|
or not material.has_meta("texture0_path")
|
||||||
|
or _wmo_material_builder == null
|
||||||
|
):
|
||||||
|
return null
|
||||||
|
|
||||||
|
var texture_paths := PackedStringArray()
|
||||||
|
var texture0_index := _append_texture_path(
|
||||||
|
texture_paths,
|
||||||
|
String(material.get_meta("texture0_path", ""))
|
||||||
|
)
|
||||||
|
var texture1_index := _append_texture_path(
|
||||||
|
texture_paths,
|
||||||
|
String(material.get_meta("texture1_path", ""))
|
||||||
|
)
|
||||||
|
var texture2_index := _append_texture_path(
|
||||||
|
texture_paths,
|
||||||
|
String(material.get_meta("texture2_path", ""))
|
||||||
|
)
|
||||||
|
|
||||||
|
var diffuse_color := Color.WHITE
|
||||||
|
var emissive_color := Color.BLACK
|
||||||
|
var secondary_color := Color.WHITE
|
||||||
|
if material is ShaderMaterial:
|
||||||
|
var shader_material := material as ShaderMaterial
|
||||||
|
var diffuse_value: Variant = shader_material.get_shader_parameter("diffuse_color")
|
||||||
|
var emissive_value: Variant = shader_material.get_shader_parameter("emissive_color")
|
||||||
|
var secondary_value: Variant = shader_material.get_shader_parameter("secondary_color")
|
||||||
|
if diffuse_value is Color:
|
||||||
|
diffuse_color = diffuse_value
|
||||||
|
if emissive_value is Color:
|
||||||
|
emissive_color = emissive_value
|
||||||
|
if secondary_value is Color:
|
||||||
|
secondary_color = secondary_value
|
||||||
|
|
||||||
|
var material_definition := {
|
||||||
|
"texture0": texture0_index,
|
||||||
|
"texture1": texture1_index,
|
||||||
|
"texture2": texture2_index,
|
||||||
|
"flags": int(material.get_meta("wow_flags", 0)),
|
||||||
|
"shader": int(material.get_meta("wow_shader", 0)),
|
||||||
|
"blend_mode": int(material.get_meta("wow_blend_mode", 0)),
|
||||||
|
"diffuse_color": diffuse_color,
|
||||||
|
"emissive_color": emissive_color,
|
||||||
|
"color2": secondary_color,
|
||||||
|
}
|
||||||
|
return _wmo_material_builder.call(
|
||||||
|
"_build_material",
|
||||||
|
material_definition,
|
||||||
|
texture_paths,
|
||||||
|
extracted_directory
|
||||||
|
) as Material
|
||||||
|
|
||||||
|
|
||||||
|
func _append_texture_path(texture_paths: PackedStringArray, texture_path: String) -> int:
|
||||||
|
if texture_path.is_empty():
|
||||||
|
return -1
|
||||||
|
var texture_index := texture_paths.size()
|
||||||
|
texture_paths.append(texture_path)
|
||||||
|
return texture_index
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bsxqkjuk77cgf
|
||||||
@@ -33,6 +33,9 @@ const WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT := preload(
|
|||||||
const WMO_SCENE_RESOURCE_FINALIZER_SCRIPT := preload(
|
const WMO_SCENE_RESOURCE_FINALIZER_SCRIPT := preload(
|
||||||
"res://src/render/wmo/wmo_scene_resource_finalizer.gd"
|
"res://src/render/wmo/wmo_scene_resource_finalizer.gd"
|
||||||
)
|
)
|
||||||
|
const WMO_RUNTIME_MESH_FINALIZER_SCRIPT := preload(
|
||||||
|
"res://src/render/wmo/wmo_runtime_mesh_finalizer.gd"
|
||||||
|
)
|
||||||
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
|
const M2_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/m2_builder.gd")
|
||||||
const M2_NATIVE_ANIMATOR_SCRIPT := preload("res://src/scenes/streaming/m2_native_animator.gd")
|
const M2_NATIVE_ANIMATOR_SCRIPT := preload("res://src/scenes/streaming/m2_native_animator.gd")
|
||||||
const STREAMING_FOCUS_SCRIPT := preload("res://src/domain/streaming/streaming_focus.gd")
|
const STREAMING_FOCUS_SCRIPT := preload("res://src/domain/streaming/streaming_focus.gd")
|
||||||
@@ -123,7 +126,6 @@ const RENDER_BUDGET_SCHEDULER_SCRIPT := preload("res://src/render/streaming/rend
|
|||||||
const REQUIRED_BAKED_TILE_FORMAT_VERSION := 5
|
const REQUIRED_BAKED_TILE_FORMAT_VERSION := 5
|
||||||
const REQUIRED_SPLAT_TILE_FORMAT_VERSION := 1
|
const REQUIRED_SPLAT_TILE_FORMAT_VERSION := 1
|
||||||
const REQUIRED_CONTROL_SPLAT_TILE_FORMAT_VERSION := 3
|
const REQUIRED_CONTROL_SPLAT_TILE_FORMAT_VERSION := 3
|
||||||
const WMO_MATERIAL_REFRESH_VERSION := 10
|
|
||||||
const RENDER_GROUND_QUERY_RAY_HEIGHT_UNITS := 5000.0
|
const RENDER_GROUND_QUERY_RAY_HEIGHT_UNITS := 5000.0
|
||||||
|
|
||||||
const TILE_SIZE := COORDINATE_MAPPER_SCRIPT.ADT_TILE_SIZE_YARDS
|
const TILE_SIZE := COORDINATE_MAPPER_SCRIPT.ADT_TILE_SIZE_YARDS
|
||||||
@@ -376,6 +378,9 @@ var _wmo_scene_resource_cache_state := WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT.new
|
|||||||
var _wmo_scene_resource_finalizer := WMO_SCENE_RESOURCE_FINALIZER_SCRIPT.new(
|
var _wmo_scene_resource_finalizer := WMO_SCENE_RESOURCE_FINALIZER_SCRIPT.new(
|
||||||
WMO_BUILDER_SCRIPT
|
WMO_BUILDER_SCRIPT
|
||||||
)
|
)
|
||||||
|
var _wmo_runtime_mesh_finalizer := WMO_RUNTIME_MESH_FINALIZER_SCRIPT.new(
|
||||||
|
WMO_BUILDER_SCRIPT
|
||||||
|
)
|
||||||
var _wmo_missing_cache: Dictionary = {}
|
var _wmo_missing_cache: Dictionary = {}
|
||||||
var _wmo_placement_resolver := WMO_PLACEMENT_RESOLVER_SCRIPT.new()
|
var _wmo_placement_resolver := WMO_PLACEMENT_RESOLVER_SCRIPT.new()
|
||||||
var _world_wmo_root: Node3D
|
var _world_wmo_root: Node3D
|
||||||
@@ -3840,7 +3845,7 @@ func _process_wmo_render_build_jobs() -> void:
|
|||||||
if build_step["operation"] == WMO_RENDER_BUILD_STEP_PLANNER_SCRIPT.OPERATION_MESH:
|
if build_step["operation"] == WMO_RENDER_BUILD_STEP_PLANNER_SCRIPT.OPERATION_MESH:
|
||||||
var mesh := meshes[selected_index] as Mesh
|
var mesh := meshes[selected_index] as Mesh
|
||||||
if mesh != null:
|
if mesh != null:
|
||||||
_refresh_cached_wmo_mesh_materials(mesh)
|
_wmo_runtime_mesh_finalizer.finalize_mesh(mesh, extracted_dir)
|
||||||
var mesh_instance := MeshInstance3D.new()
|
var mesh_instance := MeshInstance3D.new()
|
||||||
mesh_instance.name = (
|
mesh_instance.name = (
|
||||||
mesh_names[selected_index]
|
mesh_names[selected_index]
|
||||||
@@ -3871,7 +3876,7 @@ func _process_wmo_render_build_jobs() -> void:
|
|||||||
if build_step["operation"] == WMO_RENDER_BUILD_STEP_PLANNER_SCRIPT.OPERATION_MULTIMESH:
|
if build_step["operation"] == WMO_RENDER_BUILD_STEP_PLANNER_SCRIPT.OPERATION_MULTIMESH:
|
||||||
var multimesh := multimeshes[selected_index] as MultiMesh
|
var multimesh := multimeshes[selected_index] as MultiMesh
|
||||||
if multimesh != null:
|
if multimesh != null:
|
||||||
_refresh_cached_wmo_mesh_materials(multimesh.mesh)
|
_wmo_runtime_mesh_finalizer.finalize_mesh(multimesh.mesh, extracted_dir)
|
||||||
var multimesh_instance := MultiMeshInstance3D.new()
|
var multimesh_instance := MultiMeshInstance3D.new()
|
||||||
multimesh_instance.name = (
|
multimesh_instance.name = (
|
||||||
multimesh_names[selected_index]
|
multimesh_names[selected_index]
|
||||||
@@ -3950,81 +3955,18 @@ func _prepare_runtime_wmo_instance(instance: Node3D) -> void:
|
|||||||
|
|
||||||
func _refresh_cached_wmo_materials_recursive(node: Node) -> void:
|
func _refresh_cached_wmo_materials_recursive(node: Node) -> void:
|
||||||
if node is MeshInstance3D:
|
if node is MeshInstance3D:
|
||||||
_refresh_cached_wmo_mesh_materials((node as MeshInstance3D).mesh)
|
_wmo_runtime_mesh_finalizer.finalize_mesh(
|
||||||
|
(node as MeshInstance3D).mesh,
|
||||||
|
extracted_dir
|
||||||
|
)
|
||||||
elif node is MultiMeshInstance3D:
|
elif node is MultiMeshInstance3D:
|
||||||
var multimesh := (node as MultiMeshInstance3D).multimesh
|
var multimesh := (node as MultiMeshInstance3D).multimesh
|
||||||
if multimesh != null:
|
if multimesh != null:
|
||||||
_refresh_cached_wmo_mesh_materials(multimesh.mesh)
|
_wmo_runtime_mesh_finalizer.finalize_mesh(multimesh.mesh, extracted_dir)
|
||||||
for child in node.get_children():
|
for child in node.get_children():
|
||||||
_refresh_cached_wmo_materials_recursive(child)
|
_refresh_cached_wmo_materials_recursive(child)
|
||||||
|
|
||||||
|
|
||||||
func _refresh_cached_wmo_mesh_materials(mesh: Mesh) -> void:
|
|
||||||
if mesh == null:
|
|
||||||
return
|
|
||||||
if int(mesh.get_meta("wow_wmo_material_refresh_version", 0)) >= WMO_MATERIAL_REFRESH_VERSION:
|
|
||||||
return
|
|
||||||
mesh.set_meta("wow_wmo_material_refresh_version", WMO_MATERIAL_REFRESH_VERSION)
|
|
||||||
if not (mesh is ArrayMesh):
|
|
||||||
return
|
|
||||||
|
|
||||||
var array_mesh := mesh as ArrayMesh
|
|
||||||
for surface_idx in array_mesh.get_surface_count():
|
|
||||||
var rebuilt := _rebuild_cached_wmo_material(array_mesh.surface_get_material(surface_idx))
|
|
||||||
if rebuilt != null:
|
|
||||||
array_mesh.surface_set_material(surface_idx, rebuilt)
|
|
||||||
|
|
||||||
|
|
||||||
func _rebuild_cached_wmo_material(material: Material) -> Material:
|
|
||||||
if material == null or not material.has_meta("texture0_path"):
|
|
||||||
return null
|
|
||||||
|
|
||||||
var tex0_path := String(material.get_meta("texture0_path", ""))
|
|
||||||
var tex1_path := String(material.get_meta("texture1_path", ""))
|
|
||||||
var tex2_path := String(material.get_meta("texture2_path", ""))
|
|
||||||
var textures := PackedStringArray()
|
|
||||||
var tex0_index := -1
|
|
||||||
var tex1_index := -1
|
|
||||||
var tex2_index := -1
|
|
||||||
if not tex0_path.is_empty():
|
|
||||||
tex0_index = textures.size()
|
|
||||||
textures.append(tex0_path)
|
|
||||||
if not tex1_path.is_empty():
|
|
||||||
tex1_index = textures.size()
|
|
||||||
textures.append(tex1_path)
|
|
||||||
if not tex2_path.is_empty():
|
|
||||||
tex2_index = textures.size()
|
|
||||||
textures.append(tex2_path)
|
|
||||||
|
|
||||||
var diffuse := Color.WHITE
|
|
||||||
var emissive := Color.BLACK
|
|
||||||
var secondary := Color.WHITE
|
|
||||||
if material is ShaderMaterial:
|
|
||||||
var shader_material := material as ShaderMaterial
|
|
||||||
var diffuse_value: Variant = shader_material.get_shader_parameter("diffuse_color")
|
|
||||||
var emissive_value: Variant = shader_material.get_shader_parameter("emissive_color")
|
|
||||||
var secondary_value: Variant = shader_material.get_shader_parameter("secondary_color")
|
|
||||||
if diffuse_value is Color:
|
|
||||||
diffuse = diffuse_value
|
|
||||||
if emissive_value is Color:
|
|
||||||
emissive = emissive_value
|
|
||||||
if secondary_value is Color:
|
|
||||||
secondary = secondary_value
|
|
||||||
|
|
||||||
var mat_def := {
|
|
||||||
"texture0": tex0_index,
|
|
||||||
"texture1": tex1_index,
|
|
||||||
"texture2": tex2_index,
|
|
||||||
"flags": int(material.get_meta("wow_flags", 0)),
|
|
||||||
"shader": int(material.get_meta("wow_shader", 0)),
|
|
||||||
"blend_mode": int(material.get_meta("wow_blend_mode", 0)),
|
|
||||||
"diffuse_color": diffuse,
|
|
||||||
"emissive_color": emissive,
|
|
||||||
"color2": secondary,
|
|
||||||
}
|
|
||||||
return WMO_BUILDER_SCRIPT._build_material(mat_def, textures, extracted_dir)
|
|
||||||
|
|
||||||
|
|
||||||
func _cancel_wmo_build_job(tile_key: String) -> void:
|
func _cancel_wmo_build_job(tile_key: String) -> void:
|
||||||
if _wmo_build_jobs.has(tile_key):
|
if _wmo_build_jobs.has(tile_key):
|
||||||
_wmo_build_jobs.erase(tile_key)
|
_wmo_build_jobs.erase(tile_key)
|
||||||
|
|||||||
@@ -0,0 +1,292 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
## Asset-free identity, refresh-version, material-definition, ownership and
|
||||||
|
## timing regression for cached WMO runtime Mesh finalization.
|
||||||
|
|
||||||
|
const FINALIZER_SCRIPT := preload(
|
||||||
|
"res://src/render/wmo/wmo_runtime_mesh_finalizer.gd"
|
||||||
|
)
|
||||||
|
const FINALIZER_PATH := "res://src/render/wmo/wmo_runtime_mesh_finalizer.gd"
|
||||||
|
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
|
||||||
|
|
||||||
|
|
||||||
|
class FakeWmoMaterialBuilder extends RefCounted:
|
||||||
|
var definitions: Array[Dictionary] = []
|
||||||
|
var texture_path_sets: Array[PackedStringArray] = []
|
||||||
|
var extracted_directories: Array[String] = []
|
||||||
|
var rebuilt_material: Material = StandardMaterial3D.new()
|
||||||
|
|
||||||
|
func _build_material(
|
||||||
|
material_definition: Dictionary,
|
||||||
|
texture_paths: PackedStringArray,
|
||||||
|
extracted_directory: String
|
||||||
|
) -> Material:
|
||||||
|
definitions.append(material_definition.duplicate(true))
|
||||||
|
texture_path_sets.append(texture_paths.duplicate())
|
||||||
|
extracted_directories.append(extracted_directory)
|
||||||
|
return rebuilt_material
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
var failures: Array[String] = []
|
||||||
|
_verify_null_current_and_non_array_contract(failures)
|
||||||
|
_verify_unmarked_surface_contract(failures)
|
||||||
|
_verify_standard_material_definition(failures)
|
||||||
|
_verify_shader_material_colors(failures)
|
||||||
|
_verify_missing_builder_contract(failures)
|
||||||
|
_verify_source_boundaries(failures)
|
||||||
|
var elapsed_milliseconds := _verify_bounded_timing(failures)
|
||||||
|
if not failures.is_empty():
|
||||||
|
for failure in failures:
|
||||||
|
push_error("WMO_RUNTIME_MESH_FINALIZER: %s" % failure)
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
print(
|
||||||
|
"WMO_RUNTIME_MESH_FINALIZER PASS cases=27 iterations=1000 elapsed_ms=%.3f"
|
||||||
|
% elapsed_milliseconds
|
||||||
|
)
|
||||||
|
quit(0)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_null_current_and_non_array_contract(failures: Array[String]) -> void:
|
||||||
|
var builder := FakeWmoMaterialBuilder.new()
|
||||||
|
var finalizer: RefCounted = FINALIZER_SCRIPT.new(builder)
|
||||||
|
_expect_true(
|
||||||
|
finalizer.call("finalize_mesh", null, "res://data/extracted") == null,
|
||||||
|
"null Mesh remains null",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
var current_mesh := ArrayMesh.new()
|
||||||
|
current_mesh.set_meta("wow_wmo_material_refresh_version", 10)
|
||||||
|
_expect_true(
|
||||||
|
finalizer.call("finalize_mesh", current_mesh, "res://data/extracted") == current_mesh,
|
||||||
|
"current Mesh exact identity retained",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_equal(builder.definitions.size(), 0, "current Mesh skips builder", failures)
|
||||||
|
var primitive_mesh := BoxMesh.new()
|
||||||
|
_expect_true(
|
||||||
|
finalizer.call("finalize_mesh", primitive_mesh, "res://data/extracted") == primitive_mesh,
|
||||||
|
"non-ArrayMesh exact identity retained",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_equal(
|
||||||
|
int(primitive_mesh.get_meta("wow_wmo_material_refresh_version", 0)),
|
||||||
|
10,
|
||||||
|
"non-ArrayMesh stamped current",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_unmarked_surface_contract(failures: Array[String]) -> void:
|
||||||
|
var builder := FakeWmoMaterialBuilder.new()
|
||||||
|
var finalizer: RefCounted = FINALIZER_SCRIPT.new(builder)
|
||||||
|
var mesh := _mesh_with_surfaces(2)
|
||||||
|
var ordinary_material := StandardMaterial3D.new()
|
||||||
|
mesh.surface_set_material(1, ordinary_material)
|
||||||
|
var finalized_mesh: Mesh = finalizer.call(
|
||||||
|
"finalize_mesh",
|
||||||
|
mesh,
|
||||||
|
"res://data/extracted"
|
||||||
|
) as Mesh
|
||||||
|
_expect_true(finalized_mesh == mesh, "stale ArrayMesh exact identity retained", failures)
|
||||||
|
_expect_true(mesh.surface_get_material(0) == null, "null surface remains null", failures)
|
||||||
|
_expect_true(
|
||||||
|
mesh.surface_get_material(1) == ordinary_material,
|
||||||
|
"unmarked material exact identity retained",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_equal(builder.definitions.size(), 0, "unmarked surfaces skip builder", failures)
|
||||||
|
_expect_equal(
|
||||||
|
int(mesh.get_meta("wow_wmo_material_refresh_version", 0)),
|
||||||
|
10,
|
||||||
|
"stale ArrayMesh stamped current",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_standard_material_definition(failures: Array[String]) -> void:
|
||||||
|
var builder := FakeWmoMaterialBuilder.new()
|
||||||
|
var finalizer: RefCounted = FINALIZER_SCRIPT.new(builder)
|
||||||
|
var cached_material := StandardMaterial3D.new()
|
||||||
|
cached_material.set_meta("texture0_path", "World\\A.blp")
|
||||||
|
cached_material.set_meta("texture1_path", "")
|
||||||
|
cached_material.set_meta("texture2_path", "World\\C.blp")
|
||||||
|
cached_material.set_meta("wow_flags", 17)
|
||||||
|
cached_material.set_meta("wow_shader", 4)
|
||||||
|
cached_material.set_meta("wow_blend_mode", 6)
|
||||||
|
var mesh := _mesh_with_surfaces(1)
|
||||||
|
mesh.surface_set_material(0, cached_material)
|
||||||
|
finalizer.call("finalize_mesh", mesh, "res://fixture/extracted")
|
||||||
|
_expect_equal(builder.definitions.size(), 1, "eligible surface rebuilt once", failures)
|
||||||
|
_expect_true(
|
||||||
|
mesh.surface_get_material(0) == builder.rebuilt_material,
|
||||||
|
"exact builder Material adopted",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
builder.texture_path_sets[0] == PackedStringArray(["World\\A.blp", "World\\C.blp"]),
|
||||||
|
"texture paths compacted in slot order",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
builder.extracted_directories == ["res://fixture/extracted"],
|
||||||
|
"extracted directory forwarded exactly",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
var definition := builder.definitions[0]
|
||||||
|
_expect_equal(int(definition["texture0"]), 0, "texture0 compact index", failures)
|
||||||
|
_expect_equal(int(definition["texture1"]), -1, "empty texture1 index", failures)
|
||||||
|
_expect_equal(int(definition["texture2"]), 1, "texture2 compact index", failures)
|
||||||
|
_expect_equal(int(definition["flags"]), 17, "WMO flags forwarded", failures)
|
||||||
|
_expect_equal(int(definition["shader"]), 4, "WMO shader forwarded", failures)
|
||||||
|
_expect_equal(int(definition["blend_mode"]), 6, "WMO blend mode forwarded", failures)
|
||||||
|
_expect_true(definition["diffuse_color"] == Color.WHITE, "default diffuse color", failures)
|
||||||
|
_expect_true(definition["emissive_color"] == Color.BLACK, "default emissive color", failures)
|
||||||
|
_expect_true(definition["color2"] == Color.WHITE, "default secondary color", failures)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_shader_material_colors(failures: Array[String]) -> void:
|
||||||
|
var builder := FakeWmoMaterialBuilder.new()
|
||||||
|
var finalizer: RefCounted = FINALIZER_SCRIPT.new(builder)
|
||||||
|
var shader := Shader.new()
|
||||||
|
shader.code = (
|
||||||
|
"shader_type spatial; uniform vec4 diffuse_color; "
|
||||||
|
+ "uniform vec4 emissive_color; uniform vec4 secondary_color;"
|
||||||
|
)
|
||||||
|
var cached_material := ShaderMaterial.new()
|
||||||
|
cached_material.shader = shader
|
||||||
|
cached_material.set_shader_parameter("diffuse_color", Color(0.1, 0.2, 0.3, 0.4))
|
||||||
|
cached_material.set_shader_parameter("emissive_color", Color(0.5, 0.6, 0.7, 0.8))
|
||||||
|
cached_material.set_shader_parameter("secondary_color", Color(0.9, 0.8, 0.7, 0.6))
|
||||||
|
cached_material.set_meta("texture0_path", "World\\Only.blp")
|
||||||
|
var mesh := _mesh_with_surfaces(1)
|
||||||
|
mesh.surface_set_material(0, cached_material)
|
||||||
|
finalizer.call("finalize_mesh", mesh, "res://data/extracted")
|
||||||
|
var definition := builder.definitions[0]
|
||||||
|
_expect_true(
|
||||||
|
definition["diffuse_color"] == Color(0.1, 0.2, 0.3, 0.4),
|
||||||
|
"shader diffuse color forwarded",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
definition["emissive_color"] == Color(0.5, 0.6, 0.7, 0.8),
|
||||||
|
"shader emissive color forwarded",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
definition["color2"] == Color(0.9, 0.8, 0.7, 0.6),
|
||||||
|
"shader secondary color forwarded",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_missing_builder_contract(failures: Array[String]) -> void:
|
||||||
|
var finalizer: RefCounted = FINALIZER_SCRIPT.new(null)
|
||||||
|
var cached_material := StandardMaterial3D.new()
|
||||||
|
cached_material.set_meta("texture0_path", "World\\A.blp")
|
||||||
|
var mesh := _mesh_with_surfaces(1)
|
||||||
|
mesh.surface_set_material(0, cached_material)
|
||||||
|
finalizer.call("finalize_mesh", mesh, "res://data/extracted")
|
||||||
|
_expect_true(
|
||||||
|
mesh.surface_get_material(0) == cached_material,
|
||||||
|
"missing builder retains cached Material identity",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_source_boundaries(failures: Array[String]) -> void:
|
||||||
|
var loader_source := _read_text(LOADER_PATH, failures)
|
||||||
|
var finalizer_source := _read_text(FINALIZER_PATH, failures)
|
||||||
|
_expect_true(
|
||||||
|
loader_source.contains("WMO_RUNTIME_MESH_FINALIZER_SCRIPT.new("),
|
||||||
|
"loader composes runtime Mesh finalizer",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
loader_source.contains("_wmo_runtime_mesh_finalizer.finalize_mesh("),
|
||||||
|
"loader delegates runtime Mesh finalization",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
for released_token in [
|
||||||
|
"func _refresh_cached_wmo_mesh_materials(",
|
||||||
|
"func _rebuild_cached_wmo_material(",
|
||||||
|
"const WMO_MATERIAL_REFRESH_VERSION",
|
||||||
|
]:
|
||||||
|
_expect_false(
|
||||||
|
loader_source.contains(released_token),
|
||||||
|
"loader releases %s" % released_token,
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
for owned_token in [
|
||||||
|
"const MATERIAL_REFRESH_VERSION := 10",
|
||||||
|
"wow_wmo_material_refresh_version",
|
||||||
|
"array_mesh.surface_get_material(surface_index)",
|
||||||
|
"_wmo_material_builder.call(",
|
||||||
|
]:
|
||||||
|
_expect_true(
|
||||||
|
finalizer_source.contains(owned_token),
|
||||||
|
"finalizer owns %s" % owned_token,
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
_expect_true(
|
||||||
|
loader_source.contains("func _refresh_cached_wmo_materials_recursive(node: Node)"),
|
||||||
|
"loader retains scene traversal",
|
||||||
|
failures
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _verify_bounded_timing(failures: Array[String]) -> float:
|
||||||
|
var builder := FakeWmoMaterialBuilder.new()
|
||||||
|
var finalizer: RefCounted = FINALIZER_SCRIPT.new(builder)
|
||||||
|
var mesh := ArrayMesh.new()
|
||||||
|
mesh.set_meta("wow_wmo_material_refresh_version", 10)
|
||||||
|
var started_microseconds := Time.get_ticks_usec()
|
||||||
|
for iteration in range(1000):
|
||||||
|
finalizer.call("finalize_mesh", mesh, "res://data/extracted")
|
||||||
|
var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
|
||||||
|
if elapsed_milliseconds >= 1000.0:
|
||||||
|
failures.append(
|
||||||
|
"1000 current Mesh finalizations took %.3fms (budget < 1000ms)"
|
||||||
|
% elapsed_milliseconds
|
||||||
|
)
|
||||||
|
return elapsed_milliseconds
|
||||||
|
|
||||||
|
|
||||||
|
func _mesh_with_surfaces(surface_count: int) -> ArrayMesh:
|
||||||
|
var mesh := ArrayMesh.new()
|
||||||
|
var arrays: Array = []
|
||||||
|
arrays.resize(Mesh.ARRAY_MAX)
|
||||||
|
arrays[Mesh.ARRAY_VERTEX] = PackedVector3Array([
|
||||||
|
Vector3.ZERO,
|
||||||
|
Vector3.RIGHT,
|
||||||
|
Vector3.UP,
|
||||||
|
])
|
||||||
|
for surface_index in range(surface_count):
|
||||||
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||||
|
return mesh
|
||||||
|
|
||||||
|
|
||||||
|
func _read_text(path: String, failures: Array[String]) -> String:
|
||||||
|
var file := FileAccess.open(path, FileAccess.READ)
|
||||||
|
if file == null:
|
||||||
|
failures.append("cannot read %s" % path)
|
||||||
|
return ""
|
||||||
|
var source := file.get_as_text()
|
||||||
|
file.close()
|
||||||
|
return source
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_true(value: bool, label: String, failures: Array[String]) -> void:
|
||||||
|
if not value:
|
||||||
|
failures.append(label)
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_false(value: bool, label: String, failures: Array[String]) -> void:
|
||||||
|
if value:
|
||||||
|
failures.append(label)
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_equal(actual: int, expected: int, label: String, failures: Array[String]) -> void:
|
||||||
|
if actual != expected:
|
||||||
|
failures.append("%s: expected %d, got %d" % [label, expected, actual])
|
||||||
@@ -44,6 +44,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
`M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001`,
|
`M03-RND-WMO-RENDER-RESOURCE-FINALIZER-001`,
|
||||||
`M03-RND-WMO-SCENE-RESOURCE-CACHE-001`,
|
`M03-RND-WMO-SCENE-RESOURCE-CACHE-001`,
|
||||||
`M03-RND-WMO-SCENE-RESOURCE-FINALIZER-001`,
|
`M03-RND-WMO-SCENE-RESOURCE-FINALIZER-001`,
|
||||||
|
`M03-RND-WMO-RUNTIME-MESH-FINALIZER-001`,
|
||||||
`M03-RND-ADT-WATER-LOAD-PIPELINE-001`,
|
`M03-RND-ADT-WATER-LOAD-PIPELINE-001`,
|
||||||
`M03-RND-ADT-WATER-SCENE-FINALIZER-001`,
|
`M03-RND-ADT-WATER-SCENE-FINALIZER-001`,
|
||||||
`M03-RND-M2-RUNTIME-REBUILD-CLASSIFIER-001`,
|
`M03-RND-M2-RUNTIME-REBUILD-CLASSIFIER-001`,
|
||||||
@@ -154,6 +155,13 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
cache/finalizer/queue/planner/registry/resolver/shutdown, facade,
|
cache/finalizer/queue/planner/registry/resolver/shutdown, facade,
|
||||||
internal-access `30`, manifest and checkpoint dry-run `7/7`, documentation
|
internal-access `30`, manifest and checkpoint dry-run `7/7`, documentation
|
||||||
`48` and coordination.
|
`48` and coordination.
|
||||||
|
WMO runtime Mesh finalizer passed `cases=27 iterations=1000` with null/current/
|
||||||
|
non-ArrayMesh exact identity, refresh-version stamping, null/unmarked surface
|
||||||
|
retention, compact texture indices, exact flags/shader/blend/default and
|
||||||
|
ShaderMaterial colors, builder-result adoption and source boundaries. All 63
|
||||||
|
autonomous headless verifiers passed; internal-access remained `30`,
|
||||||
|
documentation covered `49` module specifications, coordination retained `34`
|
||||||
|
historical expired-claim warnings and checkpoint dry-run kept all `7/7` plans.
|
||||||
ADT water load pipeline state passed `cases=12 iterations=100
|
ADT water load pipeline state passed `cases=12 iterations=100
|
||||||
elapsed_ms=86.170`; its initial direct no-`.godot` run passed (`94.576ms`),
|
elapsed_ms=86.170`; its initial direct no-`.godot` run passed (`94.576ms`),
|
||||||
WMO caches/queue/planner/registry/resolver/material/shutdown and 18 adjacent
|
WMO caches/queue/planner/registry/resolver/material/shutdown and 18 adjacent
|
||||||
@@ -423,6 +431,11 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
request admission remain loader-owned; terminal I/O, `PackedScene` probe,
|
request admission remain loader-owned; terminal I/O, `PackedScene` probe,
|
||||||
WMOBuilder metadata validation and probe release belong to the scene finalizer.
|
WMOBuilder metadata validation and probe release belong to the scene finalizer.
|
||||||
Rejected non-Node3D probes are now freed without changing fallback or output.
|
Rejected non-Node3D probes are now freed without changing fallback or output.
|
||||||
|
Cached WMO runtime material refresh remains version `10`; exact Mesh identity,
|
||||||
|
stale stamping, surface order, compact texture path indices, cached metadata/
|
||||||
|
colors and WMOBuilder output adoption are unchanged. Those rules now belong to
|
||||||
|
the runtime Mesh finalizer while cached-scene/build-job Node traversal, permits,
|
||||||
|
attachment and lifetime remain loader-owned.
|
||||||
ADT water requests retain FIFO order and tile-key deduplication; tile release
|
ADT water requests retain FIFO order and tile-key deduplication; tile release
|
||||||
removes pending requests without cancelling active work, task completion and
|
removes pending requests without cancelling active work, task completion and
|
||||||
result publication order are unchanged, and the mutex remains limited to the
|
result publication order are unchanged, and the mutex remains limited to the
|
||||||
@@ -503,6 +516,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
cached WMO PackedScene/missing/request state and loader file-size/admission/
|
cached WMO PackedScene/missing/request state and loader file-size/admission/
|
||||||
reset adapters, plus terminal PackedScene polling/probe/lifetime/publication
|
reset adapters, plus terminal PackedScene polling/probe/lifetime/publication
|
||||||
finalizer,
|
finalizer,
|
||||||
|
WMO runtime Mesh finalizer and four loader Mesh-delegation adapters,
|
||||||
ADT water pending/task/result pipeline state and loader enqueue/start/publish/
|
ADT water pending/task/result pipeline state and loader enqueue/start/publish/
|
||||||
drain/release/reset/shutdown/metrics adapters,
|
drain/release/reset/shutdown/metrics adapters,
|
||||||
stateless ADT water scene finalizer and both loader build/attach adapters,
|
stateless ADT water scene finalizer and both loader build/attach adapters,
|
||||||
@@ -563,7 +577,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
|
|||||||
animation and static Mesh terminal ResourceLoader drains are extracted, while
|
animation and static Mesh terminal ResourceLoader drains are extracted, while
|
||||||
action execution, material-prototype lookup and root cleanup remain loader-owned;
|
action execution, material-prototype lookup and root cleanup remain loader-owned;
|
||||||
cached-scene WMO FileAccess/size admission, live fallback and group
|
cached-scene WMO FileAccess/size admission, live fallback and group
|
||||||
materialization remain in the loader; both WMO terminal I/O paths are separated;
|
materialization remain in the loader; both WMO terminal I/O paths and runtime
|
||||||
|
Mesh material refresh are separated;
|
||||||
asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending;
|
asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending;
|
||||||
ADT water parsing and active worker interruption remain loader-owned;
|
ADT water parsing and active worker interruption remain loader-owned;
|
||||||
materialization is separated but remains synchronous main-thread work behind
|
materialization is separated but remains synchronous main-thread work behind
|
||||||
|
|||||||
Reference in New Issue
Block a user