Skip to content

Feat/table top xr#3

Open
nmdurkee wants to merge 56 commits into
mainfrom
feat/table-top-xr
Open

Feat/table top xr#3
nmdurkee wants to merge 56 commits into
mainfrom
feat/table-top-xr

Conversation

@nmdurkee

Copy link
Copy Markdown
Collaborator

Standalone Quest working. Next step is to get XR and replays working

thesprockee and others added 4 commits April 12, 2026 21:28
* 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>
Copilot AI review requested due to automatic review settings April 13, 2026 12:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 XRController tag 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-mcp sourced 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.

Comment on lines +10 to +19
"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"
},

Copilot AI Apr 13, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread ProjectSettings/GraphicsSettings.asset
nmdurkee and others added 23 commits April 13, 2026 07:22
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>
nmdurkee and others added 29 commits April 13, 2026 21:15
- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants