render: extract WMO render group materializer
Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001 Agent: sindo-main-codex Tests: 63/64 autonomous verifiers passed; proprietary ADT probe unavailable; baseline dry-run 7/7; documentation and coordination gates passed Fidelity: exact behavior-preserving node materialization extraction; no new 3.3.5a parity claim
This commit is contained in:
@@ -50,6 +50,7 @@
|
||||
| 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 runtime Mesh finalizer | Implemented extraction | [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-finalizer.md) |
|
||||
| WMO render group materializer | Implemented extraction | [`wmo-render-group-materializer.md`](wmo-render-group-materializer.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) |
|
||||
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
# WMO Render Group Materializer
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Status | Implemented |
|
||||
| Target | M03 Renderer Facade and Safe Extraction |
|
||||
| Work package | `M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001` |
|
||||
| Owner | Render |
|
||||
| Last verified | 2026-08-01 |
|
||||
|
||||
## Purpose
|
||||
|
||||
`WmoRenderGroupMaterializer` creates and attaches one lightweight cached WMO
|
||||
render group on the renderer main thread. It owns the duplicated
|
||||
`MeshInstance3D`/`MultiMeshInstance3D` presentation rules that previously lived
|
||||
inside `StreamingWorldLoader`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- select a build step, advance/cancel a job or consume a render permit;
|
||||
- load, cache, validate or finalize WMO Resources;
|
||||
- resolve placement transforms or own the placement root;
|
||||
- choose Editor persistence policy or serialize generated nodes;
|
||||
- change WMO cache formats, materials, visibility or shadow policy.
|
||||
|
||||
## Context and boundaries
|
||||
|
||||
The loader obtains a queue-owned WMO root and render Resource, asks
|
||||
`WmoRenderBuildStepPlanner` for one operation, and finalizes the selected Mesh.
|
||||
The materializer then performs only indexed node presentation and attachment.
|
||||
The loader retains scheduler, queue and Editor composition responsibilities.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Queue[WmoRenderBuildQueue] --> Loader[StreamingWorldLoader]
|
||||
Planner[WmoRenderBuildStepPlanner] --> Loader
|
||||
Loader --> Finalizer[WmoRuntimeMeshFinalizer]
|
||||
Loader --> Materializer[WmoRenderGroupMaterializer]
|
||||
Materializer --> MeshNode[MeshInstance3D]
|
||||
Materializer --> MultiMeshNode[MultiMeshInstance3D]
|
||||
MeshNode --> Root[Queue-owned WMO Node3D root]
|
||||
MultiMeshNode --> Root
|
||||
Loader --> EditorOwner[Optional Editor owner assignment]
|
||||
```
|
||||
|
||||
## Public API
|
||||
|
||||
| Symbol | Role | Thread/lifetime | Failure behavior |
|
||||
|---|---|---|---|
|
||||
| `materialize_mesh_group(...)` | Create, configure and attach one indexed Mesh group | Renderer main thread; stateless after return | Null/invalid parent, null Mesh or negative index returns null |
|
||||
| `materialize_multimesh_group(...)` | Create, configure and attach one indexed MultiMesh doodad group | Renderer main thread; stateless after return | Null/invalid parent, null MultiMesh or negative index returns null |
|
||||
|
||||
Both methods preserve exact Resource identity. `group_index` selects the optional
|
||||
name and transform; missing names use the historical indexed fallback and a
|
||||
missing transform leaves `Transform3D.IDENTITY`. Positive visibility range
|
||||
applies its caller-supplied margin. Shadow mode is always applied explicitly.
|
||||
|
||||
## Inputs and outputs
|
||||
|
||||
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
||||
|---|---|---|---|---|---|
|
||||
| Input | Queue-owned WMO `Node3D` root | `WmoRenderBuildQueue` via loader | Materializer | Borrowed; not retained | One main-thread call |
|
||||
| Input | Selected `Mesh` or `MultiMesh` | WMO render Resource via loader | Materializer | Borrowed exact Resource | Parent-node lifetime after attach |
|
||||
| Input | Names, transforms and selected index | WMO render Resource/planner via loader | Materializer | Borrowed value collections | One call |
|
||||
| Input | Visibility end/margin and shadow flag | Loader quality profile | Materializer | Scalar values | One call |
|
||||
| Output | Attached `MeshInstance3D` or `MultiMeshInstance3D` | Materializer | Loader/SceneTree | Parent root owns node and Resource reference | Until placement release/world teardown |
|
||||
|
||||
Side effects:
|
||||
|
||||
- allocates exactly one Godot geometry node for valid input;
|
||||
- applies name, optional transform, shadow and optional visibility settings;
|
||||
- attaches the node exactly once to the supplied WMO root.
|
||||
|
||||
It performs no filesystem, ResourceLoader, worker, RenderingServer RID, cache,
|
||||
queue, permit, logging or direct Editor-owner mutation.
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Input[Root + Resource + indexed metadata + render settings] --> Validate{Valid root/resource/index?}
|
||||
Validate -->|no| Null[Return null; no attachment]
|
||||
Validate -->|yes| Kind{Mesh or MultiMesh method}
|
||||
Kind --> Mesh[Create MeshInstance3D]
|
||||
Kind --> Multi[Create MultiMeshInstance3D]
|
||||
Mesh --> Configure[Name + optional transform + render settings]
|
||||
Multi --> Configure
|
||||
Configure --> Attach[Attach once to WMO root]
|
||||
Attach --> Return[Return borrowed attached node]
|
||||
```
|
||||
|
||||
## Main sequence
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loader as StreamingWorldLoader
|
||||
participant Queue as WmoRenderBuildQueue
|
||||
participant Finalizer as WmoRuntimeMeshFinalizer
|
||||
participant Materializer as WmoRenderGroupMaterializer
|
||||
participant Root as WMO Node3D root
|
||||
Loader->>Queue: read front job and cursors
|
||||
Loader->>Finalizer: finalize selected Mesh
|
||||
Loader->>Materializer: materialize selected group
|
||||
Materializer->>Root: add_child(geometry instance)
|
||||
Materializer-->>Loader: attached node or null
|
||||
Loader->>Loader: optional Editor ownership
|
||||
Loader->>Queue: adopt planned cursors
|
||||
Loader->>Loader: consume one group permit
|
||||
```
|
||||
|
||||
## Dependency diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Loader[StreamingWorldLoader] --> Materializer[WmoRenderGroupMaterializer]
|
||||
Materializer --> Engine[Node3D / GeometryInstance3D / Mesh / MultiMesh]
|
||||
Materializer -. no dependency .-> IO[ResourceLoader / FileAccess]
|
||||
Materializer -. no dependency .-> Queue[WmoRenderBuildQueue]
|
||||
Materializer -. no dependency .-> Scheduler[RenderBudgetScheduler]
|
||||
Materializer -. no dependency .-> Finalizer[WmoRuntimeMeshFinalizer]
|
||||
```
|
||||
|
||||
## Ownership, threading and resources
|
||||
|
||||
- Both public methods are main-thread only because they mutate the SceneTree.
|
||||
- The caller owns the parent root; after attachment the root owns the new node.
|
||||
- The node retains the exact input Mesh or MultiMesh Resource reference.
|
||||
- The service retains no Node, Resource, RID, collection or per-group state.
|
||||
- Loader-owned optional recursive Editor ownership runs after successful return.
|
||||
|
||||
## Errors, cancellation and recovery
|
||||
|
||||
| Failure/state | Detection | Behavior | Recovery |
|
||||
|---|---|---|---|
|
||||
| Null/freed parent | Guard | Return null; allocate/attach nothing | Caller validates current queue job |
|
||||
| Null Resource | Guard | Return null | Loader advances the historically selected cursor |
|
||||
| Negative index | Guard | Return null | Planner supplies non-negative selected indices |
|
||||
| Missing name | Bounds check | Use `Group_N` or `DoodadGroup_N` | Rebuild cache metadata if desired |
|
||||
| Missing transform | Bounds check | Retain identity transform | Rebuild cache metadata if desired |
|
||||
| Placement cancellation | Loader/queue | Parent and children released by existing lifecycle | Re-request placement later |
|
||||
| Shutdown | Loader lifecycle | Service has no retained state to drain | New loader composes a new service |
|
||||
|
||||
## Configuration, capabilities and profiles
|
||||
|
||||
The service introduces no configuration or capability. It accepts the existing
|
||||
`wmo_visibility_range`, `CHUNK_SIZE` margin and `wmo_cast_shadows` values chosen
|
||||
by the loader quality profile. Blizzlike and Enhanced selection remains outside.
|
||||
|
||||
## Persistence, cache and migrations
|
||||
|
||||
No persisted data or cache format changes. The service neither reads nor writes
|
||||
WMO cache files and requires no migration or rebake.
|
||||
|
||||
## Diagnostics and observability
|
||||
|
||||
The service emits no logs or metrics. Existing `wmo_groups` queue depth, build
|
||||
permits and loader lifecycle diagnostics remain authoritative.
|
||||
|
||||
## Verification and fidelity evidence
|
||||
|
||||
- `verify_wmo_render_group_materializer.gd` covers exact Mesh/MultiMesh identity,
|
||||
indexed and fallback names, optional transforms, shadows, positive/disabled
|
||||
visibility, attachment, invalid input, source boundaries and 1,000 groups.
|
||||
- Adjacent WMO queue/planner/finalizer and checkpoint regressions protect the
|
||||
unchanged orchestration and visible output.
|
||||
- This is an exact code-motion extraction of existing Godot presentation rules;
|
||||
it adds no original-client 3.3.5a visual parity claim.
|
||||
|
||||
## Performance budgets
|
||||
|
||||
The synthetic contract requires 1,000 simple Mesh group materializations in
|
||||
under one second. Production work remains limited to one group per scheduler
|
||||
permit. Asset-backed CPU/GPU p95/p99 and long traversal remain required evidence.
|
||||
|
||||
## Extension points
|
||||
|
||||
- Asset-backed WMO traversal can measure group attachment and lifetime without
|
||||
changing this API.
|
||||
- Additional render settings belong here only when they apply equally to both
|
||||
lightweight group-node kinds and have fidelity evidence.
|
||||
|
||||
## Capability status
|
||||
|
||||
| Capability | Status | Evidence | Gap/next step |
|
||||
|---|---|---|---|
|
||||
| Mesh group materialization | Implemented extraction | Identity/name/transform/render/attachment contract | Asset-backed visual/GPU p95/p99 pending |
|
||||
| MultiMesh doodad group materialization | Implemented extraction | Identity/name/transform/render/attachment contract | Asset-backed traversal/leak evidence pending |
|
||||
| Build planning/queue progress | Loader-owned | Existing planner/queue regressions | Further orchestration extraction pending |
|
||||
| Editor persistence ownership | Loader-owned | Source-boundary contract | Editor scene-save integration evidence pending |
|
||||
|
||||
## Known gaps and risks
|
||||
|
||||
- SceneTree node creation remains synchronous main-thread work by design.
|
||||
- The synthetic fixture does not measure private WMO assets, GPU upload, portal
|
||||
visibility, original-client visuals, long traversal or leak behavior.
|
||||
|
||||
## Source map
|
||||
|
||||
| Path | Responsibility |
|
||||
|---|---|
|
||||
| `src/render/wmo/wmo_render_group_materializer.gd` | Indexed geometry-node creation, settings and attachment |
|
||||
| `src/scenes/streaming/streaming_world_loader.gd` | Composition, finalization, queue, permits, Editor ownership and lifecycle |
|
||||
| `src/tools/verify_wmo_render_group_materializer.gd` | Synthetic contract, ownership boundary and timing regression |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- [`wmo-render-build-step-planner.md`](wmo-render-build-step-planner.md)
|
||||
- [`wmo-render-build-queue.md`](wmo-render-build-queue.md)
|
||||
- [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-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)
|
||||
@@ -7,7 +7,7 @@
|
||||
| Status | Partial |
|
||||
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; M03 terrain packages; M03 M2 packages; M03 WMO placement package |
|
||||
| Owners | Renderer workstream / milestone integrator |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-m2-animation-playback`, 2026-07-18 |
|
||||
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-render-group-materializer`, 2026-08-01 |
|
||||
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
|
||||
|
||||
## Purpose
|
||||
@@ -70,6 +70,8 @@ flowchart LR
|
||||
WmoBuildStep --> Loader
|
||||
Loader --> WmoBuildQueue[WmoRenderBuildQueue]
|
||||
WmoBuildQueue --> Loader
|
||||
Loader --> WmoGroupMaterializer[WmoRenderGroupMaterializer]
|
||||
WmoGroupMaterializer --> Scene
|
||||
Native --> Parsed[Parsed tile/model data]
|
||||
Parsed --> Loader
|
||||
Loader --> Scene[SceneTree nodes]
|
||||
@@ -164,6 +166,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
|
||||
| `WmoPlacementRegistry.add_reference/release_reference/contains/active_count/diagnostic_snapshot/clear` | Internal WMO service | Owns placement-key to tile/global reference sets | Renderer main thread; map session | Empty/unknown/non-owner input is rejected without mutation |
|
||||
| `WmoRenderBuildStepPlanner.plan_step` | Internal pure WMO service | Selects one mesh-first lightweight render-group operation and next cursors | Main/any thread; stateless | Raw integer comparisons are preserved without clamping |
|
||||
| `WmoRenderBuildQueue` / `WmoRenderBuildJob` | Internal WMO pending-state service | Owns typed root/resource/cursor jobs and FIFO placement keys | Renderer main thread; map session | Invalid enqueue rejected; duplicate/stale behavior preserved |
|
||||
| `WmoRenderGroupMaterializer.materialize_mesh_group/materialize_multimesh_group` | Internal WMO scene-materialization service | Creates, configures and attaches one indexed lightweight render group | Renderer main thread; stateless after each call | Invalid parent/resource/index returns null without attachment |
|
||||
| `WmoRenderResourceCacheState` | Internal WMO cache-state service | Owns validated Resources, negative entries and pending cache paths | Renderer main thread; map/cache session | Invalid/occupied request and unknown completion are rejected |
|
||||
| `WmoRenderResourceFinalizer` | Internal WMO terminal-I/O service | Polls lightweight render requests, validates script/format and publishes Resource/missing outcomes | Renderer main thread; stateless across calls | Non-terminal retained; failed/null/wrong/stale complete missing |
|
||||
| `WmoSceneResourceCacheState` | Internal WMO cache-state service | Owns validated PackedScenes, negative entries and pending `.tscn` paths | Renderer main thread; map/cache session | Direct missing and terminal request transitions remain distinct |
|
||||
@@ -206,6 +209,7 @@ loader configuration remains transitional composition data, not a caller API.
|
||||
| Internal WMO ownership | Resolved placement key and tile/global reference key | Loader / `WmoPlacementRegistry` | Loader create/retain/final-free decisions | Registry-owned String sets; detached diagnostics | Map session or final release |
|
||||
| Internal WMO build step | Mesh/MultiMesh counts and job cursors | Loader / `WmoRenderBuildStepPlanner` | Loader materialization/cursor adapter | Fresh scalar Dictionary | One group operation |
|
||||
| Internal WMO pending build | Placement key, Node3D root, WMO Resource and cursors | Loader / `WmoRenderBuildQueue` | Loader drain and step planner adapter | Queue-owned job and strong references | Until cancel/clear/replacement |
|
||||
| Internal WMO group materialization | Parent root, exact Mesh/MultiMesh, indexed metadata and render settings | Loader / `WmoRenderGroupMaterializer` | Attached geometry node | Parent owns node and exact Resource reference | One main-thread group operation |
|
||||
| Internal WMO render cache | Normalized path, cache path and validated Resource | Loader / `WmoRenderResourceCacheState` | Loader lookup, ResourceLoader poll and build queue | State-owned Resource/path references; detached request snapshots | Until transient/full clear |
|
||||
| Internal WMO scene cache | Normalized path, `.tscn` path and validated PackedScene | Loader / `WmoSceneResourceCacheState` | Loader lookup, request poll and scene instantiation | State-owned PackedScene/path references; detached request snapshots | Until transient/full clear |
|
||||
| Internal ADT water load | Tile key, ADT path, task ID and parsed Dictionary | Loader/worker / `AdtWaterLoadPipelineState` | Loader task start, budgeted drain and finalization | State-owned records; mutex result mailbox | Request through result completion/reset |
|
||||
@@ -413,14 +417,16 @@ sequenceDiagram
|
||||
reference sets. `WmoRenderBuildStepPlanner` owns only a call-local operation
|
||||
and cursor plan. `WmoRenderBuildQueue` owns typed pending jobs, FIFO keys and
|
||||
strong root/resource references without freeing engine objects.
|
||||
`WmoRenderGroupMaterializer` owns indexed MeshInstance3D/MultiMeshInstance3D
|
||||
creation, render settings and attachment without retaining engine objects.
|
||||
`WmoRenderResourceCacheState` owns validated render Resources, negative entries
|
||||
and pending cache paths; `WmoRenderResourceFinalizer` owns its terminal
|
||||
ResourceLoader polling and script/format validation. `WmoSceneResourceCacheState`
|
||||
similarly owns validated PackedScenes, negative entries and pending `.tscn`
|
||||
paths; `WmoSceneResourceFinalizer` owns terminal ResourceLoader I/O and
|
||||
validation-probe lifetime. The loader retains request admission, FileAccess
|
||||
size checks, live fallback, materialization, permits, validity reactions and
|
||||
every placed-Node lifecycle action.
|
||||
size checks, live fallback, Mesh finalization, permits, Editor ownership,
|
||||
validity reactions and every placed-Node lifecycle action.
|
||||
- `AdtWaterLoadPipelineState` owns pending request order/deduplication, opaque
|
||||
active task IDs and the worker-safe parsed-result mailbox. The loader retains
|
||||
WorkerThreadPool start/wait, ADTLoader parsing, concurrency/finalize permits,
|
||||
@@ -558,6 +564,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
transition, completion/raw integer behavior, source ownership and bounded timing.
|
||||
- WMO render build queue contract: typed references/cursors, FIFO, duplicate
|
||||
replacement, stale-front cleanup, cancel/clear engine lifetime and bounded timing.
|
||||
- WMO render group materializer contract: exact Resource identity, indexed and
|
||||
fallback names/transforms, render settings, attachment, source ownership and
|
||||
bounded main-thread timing.
|
||||
- WMO render Resource cache contract: invalid/duplicate request rejection,
|
||||
validated/missing terminal transitions, transient/full reset, detached sorted
|
||||
diagnostics, loader-owned version validation and bounded timing.
|
||||
@@ -617,7 +626,8 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
| WMO placement resolver | Implemented extraction | Scene-free path/identity/transform/source/timing contract | Asset-backed comparison pending |
|
||||
| WMO placement registry | Implemented extraction | Scene-free ownership/lifecycle/source/timing contract | Build/resource state and asset-backed cross-tile corpus pending |
|
||||
| WMO render build step planner | Implemented extraction | Scene-free order/cursor/source/timing contract | Asset-backed traversal p95/p99 pending |
|
||||
| WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Materialization and asset-backed traversal/leak evidence pending |
|
||||
| WMO render build queue | Implemented extraction | Typed lifecycle/order/ownership/source/timing contract | Asset-backed traversal/leak evidence pending |
|
||||
| WMO render group materializer | Implemented extraction | Synthetic Resource/name/transform/render/attachment/source/timing contract | Asset-backed visual/leak/GPU p95/p99 pending |
|
||||
| WMO render Resource cache state | Implemented extraction | Scene-free lifecycle/exclusivity/source/timing plus shutdown contract | Asset-backed traversal/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 |
|
||||
@@ -700,6 +710,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
| `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_runtime_mesh_finalizer.gd` | Cached WMO runtime refresh admission, surface iteration and material reconstruction |
|
||||
| `src/render/wmo/wmo_render_group_materializer.gd` | Indexed lightweight WMO geometry-node creation, render setup and attachment |
|
||||
| `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_plan.gd` | Immutable planner result with read-only tile-key sets |
|
||||
@@ -737,6 +748,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|
||||
| `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_runtime_mesh_finalizer.gd` | WMO Mesh identity/version/material-definition/boundary/timing regression |
|
||||
| `src/tools/verify_wmo_render_group_materializer.gd` | WMO render-group Resource/name/transform/render/attachment/boundary/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_renderer_internal_access.gd` | Gameplay/EditorPlugin/registered renderer-tool boundary gate derived from private streamer fields |
|
||||
|
||||
Reference in New Issue
Block a user