Implement instanced prism rendering via Entities Graphics#573
Implement instanced prism rendering via Entities Graphics#573YsKhan61 wants to merge 24 commits into
Conversation
The instanced path (PrismRenderService) no-ops silently when the config asset, ECS world, or EntitiesGraphicsSystem is missing, so a 'still N draw calls' symptom gave no on-screen signal of WHY entities weren't drawing. Add PrismRenderService.StatusLine() — a read-only diagnosis of the exact broken link (toggle off / no asset / no world / no graphics system / ON+ents=N) — and render it as a 'Prism Path' row in the DiagnosticsHUD Render section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
PrismRenderService is the project's only runtime Entities usage in a gameplay scene, so if Unity's automatic world bootstrap doesn't produce a default world, TryEnsure() failed and every prism silently fell back to its MeshRenderer (symptom: 'ACTIVE' logged but draw calls still scale 1:1 with prism count). TryEnsure() now creates the default world on demand via DefaultWorldInitialization.Initialize when none exists — guarded on null so it can never double-create, and Initialize hooks the world's system groups (EntitiesGraphicsSystem included) into the player loop. No subscene or scene authoring is required for the instanced path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
FastGrowPrism prisms render identically with the instanced path ON or OFF (non-instanced 'Draw Mesh', MPB-driven) even though ents>0, i.e. they never get a companion render entity. Add a rate-limited (12) warning in Prism.EnsureRenderEntity reporting the exact bail reason (null renderer/mesh/ material, or Create returning invalid) so the gap can be pinned in one replay. Temporary — remove once resolved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
…adout ents>0 with draw calls ~= ents and Batches == Draw Calls means the entities are created but Entities Graphics draws each individually (no instanced batching). The batching ceiling is the distinct (mesh x material) count, which the service already caches. Append meshes=/mats= to StatusLine so we can tell in one glance whether prisms fail to share a mesh (meshes huge) or the shader variant isn't instancing (meshes/mats tiny but draws still ~= ents). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
Every spindle (tadpole bodies, gyroid branches) set a per-renderer MaterialPropertyBlock for a random _Phase, which excludes it from the SRP Batcher — so hundreds of spindles each drew as their own draw call (the ~600 un-batched BezierCurve draws in the transparent pass). Replace the MPB with a small pool of SHARED phase-variant materials (8 per base material) selected by a world-position hash: sway stays desynced, but every spindle in a phase bucket shares one material and batches into a single draw. Purely cosmetic; no ecology/gameplay change. Base material captured once so pooled reuse can't layer variants-on-variants; RestoreOriginalMaterial now assigns sharedMaterial so stable-life spindles stay batchable after birth/death. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
So it runs standalone in an empty scene (mirrors the on-demand world creation in PrismRenderService). This is the isolation harness for the 'entities don't instance-batch' investigation — pure render entities, one mesh + one material, no game code — to split a config/shader batching bug from a game-integration one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
Authored scene: camera (far-clip 5000, back at z=-1500 to view the spawn cloud) + light + a PrismRenderStressTest wired to the shared prism mesh (Prism.asset), a BlockGraph material (BlueBlockMateral), count=50000. Open and press Play to read Draw Calls / Batches / SetPass on the DiagnosticsHUD — this isolates whether Entities Graphics instancing works in this project's config, independent of any game code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
… Shore
FrogletTools/Legacy/TestingMultiplayer/{Load,Do not load} Bootstrap Scene on
Play -> Tools/Cosmic Shore/Play Mode/. Same EditorPrefs key, so the current
setting and the benchmark tool's programmatic access are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
… F10 injector - DiagnosticsHUD gains a static SetStat/ClearStats API: any system publishes rows into titled sections of the HUD instead of drawing its own OnGUI overlay (no-op in release; the HUD is editor/dev-only). - PrismRenderStressTest: OnGUI overlay removed — numbers go to the HUD. New default spawn mode routes every entity through PrismRenderService (Create/ SetVisible/SetColors/SetTransform), the same bridge game prisms use, so the Prism Path ents/meshes/mats line reflects the population and the service's own overhead is measured; useRenderService=false keeps the raw EntityManager path as the zero-overhead ceiling A/B. Configure() lets runtime code set it up before Start. - PrismStressInjector (editor/dev only, auto-spawns): press F10 in ANY scene (Menu_Main lava-lamp, benchmark scene, a race) to toggle a 50k instanced stress cloud in front of the camera, borrowing mesh + themed material from a live prism. Zero scene authoring. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
…red meshes
Root cause of the in-game draw-call storm (menu lava-lamp): every shielded
prism spent its WHOLE shielded lifetime on the GameObject renderer with a
per-prism generated octahedron mesh (SetExoticVisualActive(true) for the
duration), so the Squirrel crystal-collect rings (8 permanently-shielded
prisms per pickup) and crystal-adjacent trail prisms accumulated hundreds of
un-instanced draws with the exact Frame Debugger break 'Rendering different
meshes or submeshes with GPU instancing'.
Fix, render-side only (no gameplay/ecology change, universal — not a menu
carve-out):
- OctahedronMeshGenerator.GetSharedShieldMesh: settled-shield octahedra come
from a cache keyed by quantized (halfExtents, shieldScale). Half-extents are
the authored LOCAL collider size, so same-prefab shields share ONE mesh
(convex MeshCollider also cooks once). Stale-cache guard for domain-reload-
off play sessions.
- PrismRenderService.SetMesh: swaps a companion entity's mesh + RenderBounds.
- Prism render-mesh override: while set, the entity renders the shared
octahedron instead of meshFilter.sharedMesh; EnsureRenderEntity honors it,
Initialize clears it on pooled reuse.
- PrismOctahedronShield: the settled pose hands rendering BACK to the entity
(SetRenderMeshOverride + SetExoticVisualActive(false)); only the engage
morph and shatter overlay — genuinely per-prism geometry — keep the
GameObject renderer. Engage/disengage/bloom/shatter visuals unchanged
(same geometry, same materials; all shielded materials are BlockGraph,
already Hybrid-Per-Instance). Legacy path behavior identical.
Also in this commit:
- fix(sparrow): Sparrow Projectile Prism.prefab had an EMPTY DefaultLayerName
-> LayerMask.NameToLayer('') = -1 -> ArgumentException in Prism.Awake that
killed the Sparrow's full-auto fire loop. Prefab now uses TrailBlocks and
Prism guards invalid layer names with a warning instead of throwing.
- fix(spindle): phase-variant material cache rebuilds when a stale entry
survives play-mode exit with domain reload disabled (destroyed fake-null
materials -> magenta spindles + stalled wither cascade).
- chore(prisms): remove the TEMP LogEntityDiag diagnostic (proven unable to
fire for the real issue).
- benchmark scene: all four Cell config slots now use the menu's Blob Cell
Config so the flyable benchmark environment matches the lava-lamp ecology.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
…rk scene creator Corrections per review: - The bootstrap play-mode toggles keep their original submenu name under the new home: Tools/Cosmic Shore/Testing Multiplayer/ (was briefly 'Play Mode'). - Delete CreateBenchmarkSceneTool (+meta): BenchmarkStressTest.unity is committed to the repo — there is exactly one benchmark scene and nothing should re-create or overwrite it. Stale doc/comment references updated (BenchmarkSceneLauncher summary, Docs/SettingsSystem/ARCHITECTURE.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
CS1061: the Dictionary key type declared named elements (x,y,z,s) but the local was built as an unnamed tuple, so key.x didn't exist. Construct the tuple with names. Audited the rest of the pass for the same class of error: the only other tuple (PrismStressInjector.FindDonor) declares names in its return type and is deconstructed at the call site — correct as written. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7d51d786c3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (meshRenderer && meshRenderer.enabled) meshRenderer.enabled = false; | ||
| if (show) | ||
| { | ||
| SyncRenderMaterial(); | ||
| SyncRenderTransform(); |
There was a problem hiding this comment.
Keep entity transforms in sync for direct movers
With instancing enabled by the new Resources config, this branch disables the MeshRenderer and the entity only receives LocalToWorld when ApplyRenderPath/SyncRenderTransform runs. I checked ShapeDrawingManager.ShrinkPrismsIntoShape, which directly assigns prisms[i].transform.position and .localScale each frame (Assets/_Scripts/Controller/Environment/MiniGameObjects/ShapeDrawingManager.cs:449-450) without NotifyPositionChanged or SyncRenderTransform; those captured prisms will keep rendering at their old entity transform during/after the shrink animation. Route those direct movers through the sync API or add a transform-dirty sync.
Useful? React with 👍 / 👎.
| // but refreshed only when the prism's scale changes, so this whole- | ||
| // population recompute no longer does a transform.lossyScale parent-walk | ||
| // per prism (the ~23ms/recompute hotspot at high prism counts). | ||
| float v = prism.CachedVolume; |
There was a problem hiding this comment.
Refresh cached volume for direct scale changes
Switching the cell volume recompute to CachedVolume makes volume consumers stale for scale changes that do not go through PrismScaleManager. ShapeDrawingManager.ShrinkPrismsIntoShape directly writes transform.localScale every frame (Assets/_Scripts/Controller/Environment/MiniGameObjects/ShapeDrawingManager.cs:450) and never calls RefreshVolumeCache, so Cell.LiveVolume, phase, and dominant-domain calculations keep using the pre-shrink volume for those prisms. Either keep a live-volume fallback for uncached paths or refresh the cache wherever prism scale is assigned directly.
Useful? React with 👍 / 👎.
F10 collides with the recorder shortcut, so the stress injector is now driven by a console instead of a key: the HUD gains an input field + Run button (Enter also submits; onEndEdit gated on an actual Enter press so focus loss doesn't fire commands). Commands are pluggable via a static RegisterCommand/UnregisterCommand registry — handlers get the parsed args and return a one-line result shown in a 'Console' section and logged. HUD hotkeys (F5/F6/F7) are suppressed while the field has focus. PrismStressInjector registers 'prisms': 'prisms 50000' spawns an instanced stress cloud of that size ahead of the camera (replacing any existing cloud), 'prisms' uses the default count, 'prisms off' removes it. Same behavior as the old F10 toggle, now parameterized per-invocation. No ecology assets changed: the menu (Blob) fauna spawn probabilities were verified already 1 for all three fauna configs, the CellConfig->SpawnProfile reference chain is intact, and no commit on this branch touches fauna, spawner, or Menu_Main files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
The stress harness painted its cloud with hardcoded approximations (jade-ish/ruby-ish/gold-ish literals, dark forced to black) — the only prisms not sourced from the project's theme. The palette is now resolved from ThemeManagerDataContainerSO.ColorSet (Jade/Ruby/Gold InsideBlockColor -> _BrightColor, OutsideBlockColor -> _DarkColor — the exact mapping ThemeManager bakes into every domain block material), so injected clouds in the menu/ benchmark match real prisms per-channel. Tool scenes with no theme loaded fall back to the assigned material's authored colors (also project-authored). Color churn now pulses the theme bright color and keeps the theme dark color instead of black. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
DOTS-instanced per-instance data uploads verbatim, but the legacy path's colors went through Unity's color-space handling (every legacy prism carried a per-renderer MaterialPropertyBlock — the converted look IS the reference look). Writing the same authored numbers raw read brighter, most visibly on the dark 'outside' color, which no longer sat darker than the inside. Fix at the single entity-write boundary in PrismRenderService: ReadColor (create + material refresh), SetColors (animated), and SetTeamColors (effects) now run authored colors through GammaToLinearSpace when the project is in Linear color space; _Spread and the non-color effect params are untouched, and the legacy MPB/material path is untouched. The stress harness's direct-EntityManager mode applies the same transform via the public ApplyColorSpace helper. In-editor A/B without recompiling: console command 'prismcolors raw' reproduces the too-bright pre-fix look for newly written colors, 'prismcolors linear' forces conversion, 'prismcolors auto' returns to the project default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
…e hot paths Frame-spike diagnosis follow-ups: - PrismEffectsManager zombie audit: the periodic FindObjectsByType full-scene scans (two per audit; ms-scale with thousands of scene objects — the reported Update() spike) are replaced by O(live effects) enabled-instance registries on PrismExplosion/PrismImplosion (OnEnable add / OnDisable remove). Iterated backwards because force-deactivating a zombie removes it from the registry mid-walk. Audit semantics unchanged; editor/dev only. - ImpactorBase: per-concrete-type ProfilerMarker around AcceptImpactee (lazy — no Awake added; subclasses own theirs), so the reported ~24ms OnTrigger frames attribute to 'SkimmerImpactor.AcceptImpactee' etc. in the next capture instead of vanishing into Physics.SendEvents self-time. - CapsuleMembrane: split markers for UpdateMatrices (CPU math, scales with capsule count — subdivisions 4 ~= 2,562 capsules) vs RenderMeshInstanced (matrix-array copy to the render thread) to separate math cost from render-thread sync in the reported intermittent spike. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
…rame SnowChanger.ChangeSnowOrientation ran an O(shardCount) transform.LookAt loop (~4,189 shards in the menu cell at shardDistance=120, membrane radius 1200) INLINE in the OnCellItemsUpdated SOAP raise — which the crystal-pickup chain fires from a physics callback. Profiled as the dominant chunk of the 14.92ms self-time on the pickup frame. The raise now only rewinds a cursor; Update reorients a bounded slice (shardsPerFrame, default 128) until done — same visual result spread over a few frames, zero allocation, and the pickup frame no longer pays for the cytoplasm. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
The Squirrel crystal-pickup ring (SpawnableRings, 3 rings x 8 prisms) Instantiated 24 FastGrowPrism GameObjects inline in the impact frame and parented them under a container GO that was never destroyed - one leaked AOERingSpawner root per pickup for the session. - PrismType gains explicit values (serialization drift guard) + FastGrow=10; PrismFactory routes it to a new FastGrowPrismPool (InteractivePrismPoolManager on PrismManagers.prefab, capacity 24/max 100, same cleanup events as the other prism pools). - SpawnableRings spawns via the established PrismEventChannelWithReturnSO instead of Instantiate: pooled checkout, world-space pool parenting, no container GO. Ring prisms get a monotonic spawn-serial ownerID so IDs never collide across detonations. - Shield/danger flags are written (both ways) onto prismProperties before Initialize so pooled reuse can't leak a prior life's state and the shield engages exactly once inside Initialize. - AOEBlockSpawner destroys itself after its spawn loop, matching the AOEExplosion base contract - no more per-pickup spawner roots. - Both explosion-cooldown SOs key their static dictionaries by GetInstanceID() so destroyed impactors are never retained across scenes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
…ones Every crystal pickup Instantiated one husk per model (4 for the omni crystal), cloned a Material per husk, mutated the clone per frame from Impact's drift coroutine, then Destroyed both - allocation churn and object churn in the same frame as the pickup impact. - Impact drives its shader state (_player/_red/_velocity/_Opacity) through a MaterialPropertyBlock over the SHARED exploding material - no clone, no material Destroy. Unset properties fall back to authored values, matching the legacy per-clone semantics exactly. Pooled instances raise OnReturnToPool when the drift completes; unpooled fallbacks keep the legacy self-destroy. - New SpentCrystalPoolManager (GenericPoolManager<Impact>): one instance per spent prefab on PrismManagers (omni dummy + 4 elemental dandruffs), self-registered into a prefab-keyed registry so Crystal.Explode and Mine.Explode resolve their serialized spent prefab with zero wiring. Unregistered prefabs fall back to Instantiate. Scene changes release checked-out husks (deactivation kills the drift coroutine before it can hand itself back). - GenericPoolManager exposes a read-only Prefab accessor for the registry. - FadeIn (crystal-model respawn fade) animates _opacity via MPB with a cached Renderer - the old path cloned the material in Start and did a GetComponent<Renderer> per frame. The override clears at fade end so material swaps (activation, domain change) show authored opacity. - CrystalManager caches the intensity-selected anchor array instead of ToArray() twice per respawn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
…on reporter The Squirrel hull carries two BoxColliders, so one crystal pass raised OnTriggerEnter once per collider pair and ran the entire vessel-side crystal effect chain twice - double AOE ring detonation, double SFX, double stat writes. VesselImpactor now latches per crystal instance for 0.5s (the same window Crystal holds IsExploding), before the network RPC dispatch so host and client paths dedupe identically. VesselCollisionReporterSO was wired into the Squirrel's crystal effect list but raised EventOnSkimmerVesselCollision - a skimmer-vessel event - on every crystal pickup, polluting whatever legitimately listens to that channel (StatsManager) plus a per-pickup debug log. The event channel and its real raisers stay; the reporter class, its asset, and the container entry are deleted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
Ring prisms are conserved mass - they never return to the pool mid-game, so every pickup permanently checks out 24 and the buffer refills at ~20/s. A second pickup within ~1.2s hit the shortfall as synchronous in-frame Instantiates. Buffer target 24 -> 72 covers three chained pickups; the extra 48 instances prewarm behind the Bootstrap splash. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
First-use effects (crystal pickups, explosions, trails) hitch on in-game shader variant compilation - the one first-pickup cost pooling can't touch. AppManager now warms every ShaderVariantCollection wired on BootstrapConfigSO right after the bootstrap settle frames, while the splash overlay is still opaque and before the minimum-splash wait, so the compile cost is absorbed into the hold the player already sits through. Variants stay compiled for the session. Ships with an empty GameplayShaderWarmup collection wired (WarmUp on an empty collection is a no-op). Populate it from a representative editor play session via Project Settings > Graphics > Shader Loading > "Save to asset..." over the same path - the guid is preserved so the BootstrapConfig wiring keeps pointing at it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A
Summary
Implements the rendering half of the prism ECS migration (Phase R from
Docs/PRISM_ECS_MIGRATION.md), replacing per-prismMeshRenderer+MaterialPropertyBlockdraw calls with Entities Graphics instanced batching. This directly addresses the end-of-round frame collapse caused by N live prisms ≈ N draw calls + N SetPass operations.Key Changes
Core Rendering Bridge
PrismRenderService: Static service managing companion ECS entities for prism rendering. ProvidesCreate(),SetVisible(),SetColors(),SetTransform()APIs that the legacy GameObject path calls through. Includes master toggle (opt-in viaPrismRenderConfigSOor runtime override) and diagnostic status reporting.PrismRenderHandle: Opaque handle (Entity + epoch) stored onPrismMonoBehaviour, validated before dereferencing to catch stale references across world resets.PrismRenderProperties: Per-instance shader property overrides (_BrightColor,_DarkColor,_Spread) that Entities Graphics uploads into persistent DOTS-instancing buffers.Prism Integration
Prism.cs: AddedRenderHandle,_renderVisible,_exoticVisualActivestate. SingleSetRenderVisible()entry point routes both render paths (entity vs. GameObject).UsesEntityColorSinkproperty gates whetherMaterialStateManagerwrites to the entity or aMaterialPropertyBlock. Octahedron shield morphing (_exoticVisualActive = true) hands rendering back to the GameObject for per-frame mesh swaps.PrismOctahedronShield.cs: CallsPrism.SetExoticVisualActive()on engage/disengage to toggle between entity and GameObject rendering.MaterialPropertyAnimator.cs: ExposedCurrentBrightColor,CurrentDarkColor,CurrentSpreadfor entity path to read back the last animated state (interruption continuity).MaterialStateManager.cs: Routes color updates to eitherMaterialPropertyBlock(legacy) orPrismRenderService.SetColors()(entity path) based onPrism.UsesEntityColorSink.VFX Integration
PrismExplosion.cs&PrismImplosion.cs: Mirror the prism path — companion entities with animated shader property overrides (_Velocity,_ExplosionAmount,_Opacityfor explosions;_State,_Locationfor implosions) so 64 simultaneous effects batch into one instanced draw instead of 64 separate ones.PrismEffectsManager.cs: AddedMAX_ACTIVE_EFFECTS = 256hard ceiling (profiled at ~97ms/frame when unbounded); recycles oldest effect under extreme load.Stress Testing & Diagnostics
PrismRenderStressTest.cs: Max-prism harness spawning N render entities (no GameObjects) with per-instance color overrides. Two modes: viaPrismRenderService(measures bridge overhead) or rawEntityManager(theoretical ceiling). Publishes live stats toDiagnosticsHUD.PrismStressInjector.cs: Dev-only (F10 toggle) injects a stress cloud on top of any scene for real-world load testing without scene authoring.DiagnosticsHUD.cs: Added external stats API (SetStat(),SetSection()) so stress harnesses and perf probes publish rows instead of drawing overlays.Configuration & Fallback
PrismRenderConfigSO.asset: Opt-in toggle (defaults OFF; legacy path is baseline until ShaderGraphs expose animated properties as Hybrid Per Instance).PrismRenderService.Enabled: Resolution order: runtime override → config asset → default OFF. Logs activation and diagnostic hints.EntitiesGraphicsSystemis unavailable (https://claude.ai/code/session_013zxC135NV3WsroPRsfej1A