Feat/table top xr#3
Conversation
* main: Add CoplayDev unity-mcp package for Claude Code integration
Sets m_latencyOptimization=1 in Android OpenXR feature group (fixes 'Prioritize Input Polling' build warning for Meta Quest). Also captures Unity-generated changes from switching to Android build target: ProjectSettings, TagManager, GraphicsSettings, URP render pipeline assets, scene changes from Setup XR Table-Top tool. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OculusQuestFeature is deprecated in OpenXR 1.16.x — enabling it alongside MetaQuestFeature causes OpenXR Project Validation to fail and blocks the Android build entirely. Disable it; MetaQuestFeature covers all Quest devices and is the correct replacement. This also clears the cascade errors: - 'Double click to fix OpenXR Project Validation Issues' - BuildFailedException: OpenXR Build Failed - Application Entry Point warning (was triggered by validation failure) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- .meta files for TableTopXRController, TableTopPlayPauseButton, SetupXRTableTop (Unity generates these on first import) - UniversalRenderPipelineGlobalSettings 1.asset (Unity duplicate created when switching to Android build target) - Final XR/ProjectSettings tweaks written by Unity after successful Quest build verification Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates Unity project/XR/URP configuration to support an XR “table-top” workflow (Quest-focused), including required tags, URP global settings wiring, and OpenXR/PlayerSettings adjustments.
Changes:
- Adds an
XRControllertag and updates XR/OpenXR-related project settings (including preloaded XR assets). - Switches URP Global Settings reference to a newly added URP global settings asset and updates URP renderer assets / volume profile content.
- Updates package lock to include
com.coplaydev.unity-mcpsourced from a git URL.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| ProjectSettings/TagManager.asset | Adds XRController tag for XR controller identification. |
| ProjectSettings/SceneTemplateSettings.json | Updates scene template dependency type info format/entries. |
| ProjectSettings/ProjectSettings.asset | Tweaks PlayerSettings (preloaded XR assets, Android batching, Metal flag). |
| ProjectSettings/GraphicsSettings.asset | Repoints URP Global Settings map to a new asset guid. |
| Packages/packages-lock.json | Adds lock entry for com.coplaydev.unity-mcp from git. |
| Assets/XR/Settings/Open XR Package Settings.asset | Updates OpenXR feature/settings toggles for Android. |
| Assets/UniversalRenderPipelineGlobalSettings 1.asset.meta | Adds meta for new URP global settings asset. |
| Assets/UniversalRenderPipelineGlobalSettings 1.asset | Adds new URP global settings asset used by GraphicsSettings. |
| Assets/Scripts/TableTopXRController.cs.meta | Adds missing .meta for script GUID stability. |
| Assets/Scripts/TableTopPlayPauseButton.cs.meta | Adds missing .meta for script GUID stability. |
| Assets/Scripts/Editor/SetupXRTableTop.cs.meta | Adds missing .meta for editor tool script GUID stability. |
| Assets/Render/URP-Medium.asset | Updates URP shader prefilter/stripping-related fields. |
| Assets/Render/URP-Low.asset | Updates URP shader prefilter/stripping-related fields. |
| Assets/Render/URP-High.asset | Updates URP shader prefilter/stripping-related fields. |
| Assets/DefaultVolumeProfile.asset | Updates/restructures default post-processing volume profile components. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "com.coplaydev.unity-mcp": { | ||
| "version": "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#main", | ||
| "depth": 0, | ||
| "source": "git", | ||
| "dependencies": { | ||
| "com.unity.nuget.newtonsoft-json": "3.0.2", | ||
| "com.unity.test-framework": "1.1.31" | ||
| }, | ||
| "hash": "73eb27aeccfa8e0676eaf3304136e9b85953d913" | ||
| }, |
There was a problem hiding this comment.
The new com.coplaydev.unity-mcp dependency is referenced via a moving git branch (#main). Even though the lock file includes a hash, using a branch ref makes dependency resolution non-deterministic if packages-lock.json is regenerated or the hash is updated automatically. Prefer pinning the dependency to an immutable tag or commit SHA in the URL to keep builds reproducible.
Brings in latest upstream changes from feat/table-arena so the GitHub PR merges cleanly with no conflicts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PassthroughBridge.cs: thin OpenXRFeature subclass (no attribute, not registered) that exposes SetEnvironmentBlendMode(AlphaBlend) publicly. This is the only way to reach the protected-static OpenXR blend mode API from outside the feature system. TableTopXRController.EnablePassthrough(): calls PassthroughBridge to set AlphaBlend, sets URP camera to transparent solid-color clear, and disables post-processing (which can write opaque alpha and block passthrough). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Calling the native blend mode function before the XR display subsystem is running blocks the loading screen indefinitely on Quest. SetCameraTransparent() runs immediately (safe, no XR calls). EnablePassthroughWhenReady() coroutine polls XRDisplaySubsystem.running each frame and only calls PassthroughBridge.SetBlendModeAlpha() once the session is actually up, with a 30s timeout as a safety net. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ature TableTopPassthroughFeature: registered OpenXRFeature that declares XR_FB_passthrough in its extension strings so the Quest runtime enables it in the session. OnInstanceCreate loads xrCreatePassthroughFB and xrPassthroughStartFB via xrGetInstanceProcAddr. OnSessionCreate creates the passthrough handle; OnSessionBegin starts it and sets AlphaBlend. SetupXRTableTop: calls FeatureHelpers.RefreshFeatures then enables TableTopPassthroughFeature for Android automatically on tool run. TableTopXRController: simplified — passthrough is now fully handled by the feature; controller only sets camera clear flags. PassthroughBridge: deleted (no longer needed). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…/CS0246 compile errors The OpenXRFeatureAttribute is editor-only. Moving the attribute and its using directives inside #if UNITY_EDITOR blocks the class from compiling correctly for all platforms while still registering the feature in the editor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Set m_enabled: 1 for TableTopPassthroughFeature in OpenXR Package Settings so XR_FB_passthrough extension is actually requested at session start - Add AndroidManifest.xml declaring com.oculus.feature.PASSTHROUGH required by Meta Quest runtime for passthrough to activate Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…erge failure The activity name conflict broke the Gradle manifest merger. Keep only the uses-feature declaration for passthrough — Unity generates the rest. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Declare UnityPlayerGameActivity (androidApplicationEntry=1) with VR intent filter and focus-aware meta-data so Gradle merge has a named activity target instead of the unnamed element MetaQuestFeature injects - Remove old UnityPlayerActivity from xrmanifest.androidlib via tools:node=remove - Add com.oculus.supportedDevices meta-data for Quest 3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without action.MAIN + category.LAUNCHER the activity has no entry point and Unity's deploy step fails with DeploymentOperationFailedException. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merged manifest had android:enabled="false" on UnityPlayerGameActivity, preventing Android from launching the app. Use tools:replace to override. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unity 6 manages activity intent filters itself. Our tools:node=merge/remove blocks were fighting Unity's own generation. Keep only: - passthrough uses-feature - com.oculus.supportedDevices meta-data - android:enabled=true override on GameActivity via tools:replace Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GameActivity (value=1) is generated with android:enabled=false by Unity 6 and cannot be overridden from a plugin manifest. Switching to classic Activity (UnityPlayerActivity, value=0) which Unity generates as enabled=true and is already declared in xrmanifest.androidlib. Simplify AndroidManifest to just the passthrough feature + supported devices. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Revert androidApplicationEntry to 1 (GameActivity, required by Unity 6) - Add AndroidManifestPostProcessor (IPostGenerateGradleAndroidProject) that patches the launcher manifest after Unity generates the Gradle project, setting android:enabled=true on UnityPlayerGameActivity so the app can be launched by Android and the Quest home screen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The field is a bitfield: bit0=Activity, bit1=GameActivity. Value 2 = GameActivity only, which is what Unity 6 requires. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ature MetaQuestFeature injects an unnamed <activity> with com.oculus.intent.category.VR into unityLibrary manifest. Gradle requires android:name as merge key, causing 'Missing name key attribute' build failure every time. AndroidManifestPostProcessor now: - Finds all nameless <activity> elements in unityLibrary manifest - Moves their intent-filters/meta-data onto UnityPlayerGameActivity - Removes the nameless elements - Sets android:enabled=true on GameActivity Also simplify plugin AndroidManifest to uses-feature only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t-processor With androidApplicationEntry=2, Unity generates the unityLibrary manifest without a named GameActivity - only the nameless VR activity from MetaQuestFeature. Post-processor now also adds MAIN/LAUNCHER intent-filter and com.oculus.vr.focusaware meta-data when missing, so Android can find and launch the activity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UnityPlayerGameActivity extends AppCompatActivity and requires a Theme.AppCompat theme. Without it the app crashes immediately with 'You need to use a Theme.AppCompat theme'. Add styles.xml defining UnityTheme and wire it to the activity via post-processor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unity 6 removed Assets/Plugins/Android/res support. Reference @style/Theme.AppCompat.NoActionBar directly in the manifest — it's always available since UnityPlayerGameActivity extends AppCompatActivity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously AlphaBlend was only set if native xrPassthroughStartFB succeeded. If function pointers failed to load (silent early return), passthrough was never enabled. Quest 3 supports XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND natively via standard OpenXR without needing the FB extension calls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ession) Logcat confirmed AlphaBlend was available but OPAQUE was selected, because SetEnvironmentBlendMode was called in OnSessionBegin — after xrBeginSession had already locked in the blend mode. OnSessionCreate fires before xrBeginSession so the blend mode preference is applied correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PassthroughAlphaRendererFeature + PassthroughClearAlpha.shader clear the alpha channel to 0 before opaque rendering so the Quest compositor sees transparent background pixels and composites passthrough through them. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Serialize clearAlphaShader field so Unity bundles it in device builds (Shader.Find of Hidden/ shaders returns null at runtime without this) - Wire shader GUID into URP renderer asset YAML - Replace TransformObjectToHClip with direct clip-space passthrough in vertex shader to avoid per-eye VP transform corrupting the fullscreen quad - Add UNITY_VERTEX_OUTPUT_STEREO / UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO for Quest single-pass stereo (multiview) support - Switch Execute from DrawMesh to CoreUtils.DrawFullScreen which handles XR stereo correctly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iate RT - Implement RecordRenderGraph (URP 17 / Unity 6 primary path) alongside Execute (legacy fallback). Without RecordRenderGraph, URP 17 may silently skip the pass in the render graph pipeline. - Add Debug.Log in both paths so we can confirm which path fires on device. - Set m_IntermediateTextureMode: 0 (Auto) instead of 1 (Always). With Always, URP renders to an intermediate RT then blits to the XR swapchain — that blit can overwrite alpha. Auto lets URP render directly to the XR eye textures when possible (no post-processing, no MSAA RT swap). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move ClearAlpha pass to AfterRendering+10 and use ColorMask RGBA to
overwrite EVERYTHING with alpha=0 as a diagnostic. Expected outcomes:
- Full passthrough visible (even through geometry) → alpha reaches the
compositor correctly; need to fix BeforeRenderingOpaques approach
- Still black → alpha not reaching compositor; fundamental submission issue
- Disable MSAA in URP-High and URP-Medium (m_MSAA: 1). MSAA forces an
intermediate RT whose resolve step can strip the alpha channel before
the XR swapchain submission.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ssues - If headset shows RED: our pass write reaches the XR swapchain. The previous black with alpha=0 means passthrough feed isn't active (xrPassthroughStartFB may have failed). Fix: target rendering. - If headset shows BLACK: a later blit replaces our write. The RT at AfterRendering+10 is an intermediate, not the XR swapchain. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pass Root cause: 4 cameras active at depth=0. Any camera rendering after the XR camera (with ClearFlags.Skybox = black + alpha=1) overwrites the alpha=0 background the XR camera establishes, blocking passthrough. - DisableOtherCamerasFromVR(): sets stereoTargetEye=None on every camera except xrCamera, stopping them from submitting frames to the HMD while leaving them active for any non-VR purpose. - Restore ClearAlpha shader to ColorMask A / alpha=0 (no longer diagnostic). - Restore render pass event to BeforeRenderingOpaques. - Remove per-frame Debug.Log spam from the renderer pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ic file - Use camera.enabled=false instead of stereoTargetEye=None — more reliable guarantee that no other camera renders and overwrites the XR output. - Write camera list to Application.persistentDataPath/passthrough_cameras.txt so we can adb pull it after a session to see exactly which cameras were active. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e hook Root cause confirmed via logcat: PT is: ON numLayers: 0. Quest 3 requires an explicit XrCompositionLayerPassthroughFB composition layer submitted in xrEndFrame even in AlphaBlend mode. xrPassthroughStartFB alone only starts camera capture, not display compositing. Changes: - Add xrCreatePassthroughLayerFB + xrPassthroughLayerResumeFB calls - Hook xrGetInstanceProcAddr → intercept xrEndFrame to prepend XrCompositionLayerPassthroughFB at layer index 0 (background) - Enable unsafe code (required for XrFrameEndInfo pointer manipulation) - Clear static layer handle in OnSessionEnd to stop injection cleanly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same logic as previous commit but using GCHandle.Alloc(Pinned) and ref parameters instead of unsafe pointers, so the Unity project's allowUnsafeCode setting is not required. - ref XrFrameEndInfo in delegate replaces XrFrameEndInfo* pointer - GCHandle.Alloc(Pinned) pins passthrough layer struct and layers array - Marshal.ReadIntPtr reads existing layer pointers from native memory - Revert allowUnsafeCode back to 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds diagnostic logging on the first intercepted xrEndFrame to show: - how many layers Unity submits, their types and flags - whether BLEND_TEXTURE_SOURCE_ALPHA_BIT is already set Also patches any XR_TYPE_COMPOSITION_LAYER_PROJECTION layers to add XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT (flag=2) if missing. This tells the compositor to use the eye texture's alpha channel to blend rendered content with the passthrough layer (alpha=0 → passthrough, alpha=1 → rendered scene). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Debug.Log is suppressed in release builds and was never reaching logcat. Added file-based logging (passthrough_feature_diag.txt) in the OpenXR feature so every lifecycle step and return code is readable via adb pull regardless of build type. Also log the first xrEndFrame call outcome. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add logging of the passthrough layer struct contents as seen in native memory (type, next, flags, space, layerHandle, address) and log the xrEndFrame return code so we can tell if the runtime is rejecting the submitted layer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three changes to pin down the alpha channel issue: 1. PassthroughClearAlpha shader: ColorMask RGBA, write blue (0,0,1,0) so the result is visually distinct: blue=shader runs but alpha=1, passthrough feed=alpha=0 works correctly, black=shader not running. 2. Move renderer feature to AfterRenderingPostProcessing so no later URP pass can overwrite the alpha write. 3. AddRenderPasses now writes a file diagnostic so we can confirm the feature is being enqueued even in a release build. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two-step menu tool under Tools → Setup OVR Passthrough: Step 1 adds com.meta.xr.sdk.all 85.0.0 and com.unity.xr.oculus 4.5.4 to manifest.json (both already in local cache, no download needed) and triggers package resolution. Step 2 runs automatically after the reload. Step 2 (also runnable standalone): - Switches Android XR loader from OpenXR to Oculus - Adds OVRManager with isInsightPassthroughEnabled=true - Adds OVRPassthroughLayer (underlay, reconstruction) - Sets camera clearFlags=Depth, backgroundColor=(0,0,0,0), postProcessing=off - Disables PassthroughAlphaRendererFeature from URP renderer - Reverts diagnostic shader back to ColorMask A / alpha=0 This replaces the manual raw-OpenXR xrEndFrame hook approach with the battle-tested Meta SDK path used in the working Outdoor sword test project. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
XRPackageMetadataStore is not available in this version of XR Management. Replace with direct SerializedObject manipulation of the m_Loaders list, and a FindLoaderAsset helper that searches the asset database for an OculusLoader by type name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… fix eyeFovPremultiplied Three bugs blocked passthrough: 1. XRGeneralSettings still pointed to OpenXR loader (GUID mismatch) — now points to Oculus Loader.asset 2. OculusProjectConfig had _insightPassthroughSupport=Optional and isPassthroughCameraAccessEnabled=0 — both now enabled/required 3. eyeFovPremultipliedAlphaModeEnabled was never set to false — added Awake() to TableTopXRController Also fixes FindLoaderAsset() to match by asset name (strips spaces) so "Oculus Loader" matches "OculusLoader". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EnablePassthroughRuntime(): calls OVRManager.instance.isInsightPassthroughEnabled=true at runtime (property setter, not just serialized field), explicitly enables OVRPassthroughLayer, hides guardian boundary — mirrors reference PassthroughEnabler - SetCameraTransparent(): disable allowHDR so URP uses RGBA8 not R11G11B10 (R11G11B10 has no alpha channel, which blocks passthrough compositing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Writes OVRPlugin.initialized, passthrough supported/initialized, OVRPassthroughLayer state, and camera settings to tabletop_xr_diag.txt in persistentDataPath — readable via adb without logcat. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OVROverlay.OverlayType enum: None=0, Underlay=1, Overlay=2. The setup tool set overlayType=0 (None/disabled) thinking it meant Underlay. The passthrough layer was silently doing nothing the entire time. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Meta XR SDK (com.meta.xr.sdk.all 85.0.0) + Oculus XR Plugin (com.unity.xr.oculus 4.5.4) to enable passthrough AR on Quest 3. Key changes: - Packages/manifest.json: add Meta XR SDK and Oculus XR Plugin - XRGeneralSettings: switch Android XR loader from OpenXR → OculusLoader - OculusProjectConfig: _insightPassthroughSupport=Required, passthrough enabled - URP-Pipe_Renderer: disable old PassthroughAlphaRendererFeature - PassthroughClearAlpha.shader: revert diagnostic changes (ColorMask A, alpha=0) - OculusRuntimeSettings, OVRBuildConfig: auto-generated by Meta XR SDK The passthrough was broken by OVROverlay.OverlayType enum mismatch: None=0, Underlay=1, Overlay=2. Setup tool was setting 0 (None/disabled). Fixed to 1 (Underlay). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Assets/StreamingAssets/Replays/ with manifest.txt for Android-compatible file enumeration (replay file itself is >100 MB, not tracked in git — place manually before building) - DemoStart.Start() converted to coroutine; copies bundled replays from StreamingAssets to persistentDataPath/Replays/ on first run via UnityWebRequest (required since APK StreamingAssets aren't file-accessible) - Remove MakePlayerTranslucent and its calls — BIRP shader keywords don't work in URP and were making player meshes invisible on Quest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
arenaAnchor.localScale is set to 0.01 at runtime by TableTopXRController. playerObjsParent was a child of arenaAnchor, so all player objects inherited that 0.01 scale — their skinned meshes appeared ~1.8 cm tall and were invisible. The trail still rendered because TrailRenderer width is world-space and unaffected by object scale. The disc and arena geometry render correctly because they are not under arenaAnchor. Fix: make playerObjsParent a root-level scene object so instantiated players render at full scale alongside the disc. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ble players" This reverts commit 88238f5.
Standalone Quest working. Next step is to get XR and replays working