Add cross-platform AR and VR support#5335
Conversation
Adds two new core packages behind the existing optional-platform-feature
patterns:
com.codename1.ar - augmented reality backed by ARKit (iOS) and ARCore
(Android): world tracking, plane detection, screen-point hit testing,
anchors, glTF/Mesh content placement, light estimation, image tracking
and face tracking. The SPI (com.codename1.impl.ARImpl) mirrors
CameraImpl with one impl per session and a Display.getARBackend()
accessor; all events are marshaled to the EDT with per-id coalescing of
high-frequency refinements.
com.codename1.vr - pure-core VR on the portable gpu pipeline: VRView
stereo rendering with per-eye cameras, HeadTracker sensor fusion
(deterministic complementary OrientationFilter over gyro/accel/
magnetometer), VRCameraRig eye math and a Media360View equirectangular
panorama viewer with drag/gyro look. No platform dependency, so it runs
wherever the GPU backend does, including the simulator.
Platform backends:
- iOS: CN1AR.{h,m} wraps an ARKit ARSession composited through an
ARSCNView; anchored content arrives as raw interleaved vertex buffers
and becomes SCNGeometry. Gated by INCLUDE_CN1_AR (compiled out on
tvOS/watchOS with unconditional stub bridge symbols so non-AR apps
link cleanly).
- Android: typed ARCore code in the deletable
com.codename1.impl.android.ar package (camera background + mesh
renderer on a GLSurfaceView); AndroidImplementation reaches it via
reflection only, and the build deletes the package for non-AR apps.
- Simulator: JavaSEARImpl renders a virtual room (detected floor/wall
planes, mouse-look + WASD camera, ray-cast hit testing) through the
software 3D device, plus a Simulate > AR Simulation window to drive
tracking failures, lighting, image detection and face toggling.
Build pipeline: referencing com.codename1.ar injects the camera
permission/plist, links ARKit/SceneKit on iOS (weak-linked on the
tv/watch/mac slices) and adds the ARCore gradle dependency, the
android.hardware.camera.ar feature and the com.google.ar.core manifest
meta-data on Android (a new androidMetaData capability in
AiDependencyTable). The android.ar.required build hint flips the app
from AR-optional to AR-only.
Supporting gpu additions: Quaternion math utilities, a tessellated
sphere primitive with equirectangular UVs, and device-free GltfLoader
overloads that parse geometry and decode the base-color image without a
GraphicsDevice.
Covered by unit tests in maven/core-unittests (AR session/event
bridge/pose math, VR fusion determinism and camera rig, sphere
geometry, glTF device-free parsing) and two new developer guide
chapters including the simulator debugging workflow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Cloudflare Preview
|
|
Compared 144 screenshots: 144 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
|
Compared 141 screenshots: 141 matched. |
|
Compared 141 screenshots: 141 matched. |
|
Compared 140 screenshots: 140 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 135 screenshots: 135 matched. |
|
Compared 142 screenshots: 142 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 216 screenshots: 216 matched. |
|
Compared 139 screenshots: 139 matched. |
|
Compared 137 screenshots: 137 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
- The Ant build compiles the core against the CLDC bootclasspath which has no Math.acos/atan2; route Quaternion.slerp and the OrientationFilter corrections through com.codename1.util.MathUtil. - SpotBugs: extract ARSession's hit-test error forwarder into a named static class and drop the unread textureFromSource field from Media360View. - Developer guide: resolve the Vale and LanguageTool findings in the two new chapters (contractions, heading capitalization, adverbs, hyphenation, sentence restructuring) and accept the raycast / equirectangular terms. - run-ios-native-tests.sh: when xcodebuild -downloadPlatform iOS fails with "Unable to connect to simulator" (wedged CoreSimulator service on some runner instances), restart the service and retry instead of silently continuing into a doomed destination match; when falling back to an existing simulator, prefer a device on the newest iOS runtime since older-runtime devices are rejected by destination matching. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Compared 12 screenshots: 12 matched. |
The EDT-completion Runnables in IOSARImpl captured only locals, so SpotBugs (which gates the ios module too) flagged them as SIC_INNER_SHOULD_BE_STATIC_ANON. Replace both with a named static CompleteOnEdt helper and mirror the same shape in AndroidARImpl for consistency (it is compiled inside user app builds). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The core is gated on PMD rules that forbid volatile; replace every volatile field with the locking convention the codebase already uses: - ARSession: the cross-thread stop signal moves into the event bridge, guarded by the bridge lock the producers already hold; the session's own closed flag stays EDT-confined like CameraSession. - VRView / Media360View: the settings the EDT writes and the render thread reads each frame (stereo, tracking, look angles, pending image/texture source, clear color, position) move behind a state lock with a single per-frame snapshot on the render thread. Also resolves the remaining findings: foreach conversions in ARSession's dispatch loops, an intent-documented suppression for the identity comparison in ARAnchor.setNode (the Camera precedent), dropping the identity-compare fast path in Quaternion.integrateGyro (self-copy is harmless), removing OrientationFilter's redundant constructor and making VRCameraRig's scratch vector a local. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- run-mac-native-ui-tests.sh: the Mac Catalyst destination of an SDKROOT=iphoneos target needs the iOS platform installed in the active Xcode. Some runner instances ship Xcode without it, so no amount of destination warming helps - the scheme lists only watchOS/tvOS destinations and the build dies with "Unable to find a destination". Detect the missing iphoneos SDK, download the platform and restart a wedged CoreSimulator service when the download fails with "Unable to connect to simulator" (the same recovery run-ios-native-tests.sh gained). Verified locally that the generated project itself is healthy: the Catalyst destination lists and the slice builds with the AR natives. - SimulatorWindowModeVerifier: the fixed warmup sleep raced the simulator's first paint on slow runners, failing the run with "Screenshot appears blank/flat". Keep the scenario warmup as a floor and then poll the desktop capture until it shows actual content before validating. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Compared 142 screenshots: 142 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
The legacy Ant project compiles all of Ports/Android/src without the ARCore dependency; mirror the maven-compiler exclusion so the deletable com.codename1.impl.android.ar package (compiled inside user app builds where the gradle dependency exists) stays out of the port jar here too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The custom jar target in Ports/Android/build.xml runs its own raw javac over the port sources, bypassing the nbproject excludes property; give it the same com/codename1/impl/android/ar exclusion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds end-to-end screenshot coverage for the com.codename1.vr stereo and 360 viewers plus an assertion test for the com.codename1.ar contract, an AR scenario for the JavaSE simulator integration harness, and the first developer-guide images for both chapters. - VRStereoSceneScreenshotTest / Media360PanoramaScreenshotTest render a fixed VR stereo scene and a generated equirectangular panorama through the same GPU peer and ready-callback pattern as the Gpu3D tests, with head tracking frozen for determinism. They capture wherever CN.isGpuSupported() is true (the five 3D-capable cn1ss platforms). - ARApiTest exercises the AR API against whichever port backend is present: the unsupported contract on the CI platforms (no AR runtime) and a full session round trip when a backend exists. No screenshot. - The javase-simulator harness gains an ar-demo scenario that opens a simulated session, waits for the virtual floor plane, hit-tests it and anchors a model, asserting the full EDT event pipeline ran. - Primitives.sphere flips u for inside-out spheres so 360 panoramas read correctly instead of mirror-imaged, covered by a new SpherePrimitiveTest case. - Wires the AR simulator and VR stereo images into the guide chapters. Goldens for the new screenshot tests (five cn1ss platforms) and the ar-demo baseline are adopted from the CI capture in a follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the VRStereoScene and Media360Panorama goldens captured by the first CI run on the five 3D-capable cn1ss platforms (mac-native, android, javascript, windows, and linux x64 + arm64) plus the javase-single-ar-demo baseline from the JavaSE simulator integration suite. The GPU-backed platforms carry a tolerance sidecar (matching the Gpu3D convention) since software rasterizers vary slightly between runners; the linux native goldens are bit-exact and need none. Also wires the CI-captured 360 panorama into the Virtual Reality chapter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lakes The simulator skin is resolved through an unauthenticated api.github.com release query, whose quota is 60 requests/hour per IP. A PR that fans out many jobs (plus re-runs) can exhaust that on a shared runner IP, failing skin resolution with "HTTP Error 403: rate limit exceeded" before the suite even starts. Pass the workflow's GITHUB_TOKEN (already in the step env) as a Bearer header when present, raising the quota to 5000/hour; the call still works unauthenticated when no token is available (local runs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Android instrumentation @test is a trivial launcher; the CN1SS suite runs asynchronously in the app process and signals completion with the CN1SS:SUITE:FINISHED marker, which the runner script waits for before comparing screenshots. That wait was 60s, which left almost no margin: master detects the marker with a few seconds to spare, but adding two GPU screenshot tests (VRStereoScene, Media360Panorama) - each a few seconds of readback on the emulator's software rasterizer - pushed completion just past 60s. The marker was then never detected in time, the app was force-stopped mid-suite, and the last two tests (DesktopMode, VideoIODecodedFrames) reported missing screenshots. Raise the wait to 240s. A suite that finishes sooner still breaks out immediately on marker detection (so the healthy path is unchanged), the extra ceiling only applies while a larger suite finishes, and a genuine hang is still caught by the missing-screenshot count guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses review feedback on the immersive screenshot tests: - Stereo VR targets a headset; on tvOS it is redundant, so VRStereoScene now self-skips there. Media360Panorama still runs on tvOS, where panning a 360 image is a sensible use (its tvOS golden is adopted from CI). - Both views now capture in landscape, where a stereo scene and a panorama read naturally. A small LandscapeCapture helper locks landscape and waits for the rotation to land on phones, and is a no-op on desktop/browser/tvOS (guarded by canForceOrientation), so only the phone goldens change orientation. - Both tests move to the suite tail, after the orientation test and every portrait screenshot, and leave the device in landscape - safe because the VideoIO tests that follow capture off-screen grids or make no capture. The existing VR/360 goldens are removed; the new landscape captures (and the tvOS Media360Panorama golden) are adopted from the CI run that follows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ARApiTest opened a real AR session in its supported branch. On Android that routes into AndroidARImpl.open(), which calls ArCoreApk.requestInstall(): when ARCore is absent - the CI emulator's state - that launches the Play Store install flow and backgrounds the app before AR.open() throws. The thrown exception is caught by the runner, but the backgrounding freezes the whole screenshot suite: the app never resumes, so every test after ARApiTest (DesktopMode, VideoIODecodedFrames, and the VR/360 captures) never runs and CN1SS:SUITE:FINISHED is never emitted. It looked like an intermittent Android hang because ArCoreApk.checkAvailability() - hence AR.isSupported() - varies per run, so the open path was hit only some of the time. The full live-session round trip is covered where it is safe and deterministic: the JavaSE simulator integration test drives JavaSEARImpl end to end, and the unit tests exercise RecordingARImpl. ARApiTest now only verifies what is safe on every port - the capability query is non-null and the unsupported contract holds (where AR.open throws before reaching any backend) - and never opens a live session. Reproduced locally on an arm64 emulator booted with -gpu swiftshader_indirect: with this change the suite runs to completion and the VR/360 views capture in landscape on Android. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reposition to the suite tail put the VR/360 tests immediately before VideoIODecodedFrames and left the device in landscape, and that test then exceeded even its generous tolerance on Android - a GPU-heavy neighbor plus the leftover orientation perturbed its decoded-frame grid. Move the two immersive tests to right after the orientation test (which is also where the review asked for them) so the video tests keep their master-identical context: portrait, and no GPU-heavy test next to them. The last of the two (Media360Panorama) now restores portrait once its capture is done via LandscapeCapture.restorePortrait, waiting for the rotation to land, so every test after it runs portrait exactly as before. Landscape locking and the restore remain no-ops on desktop/browser/tvOS (guarded by canForceOrientation), so only the phone captures rotate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On the iOS Metal backend the DesktopMode screenshot grabbed the VR/360 form's lingering 3D layer under the late-present race (it captured the "360 Panorama" form's stale, black Metal drawable instead of DesktopMode's own). Both immersive tests now remove their view once the capture is done - detaching the GPU peer so no stale layer survives to be grabbed by a later test - and Media360Panorama then restores portrait as before. Seeds the landscape VR/360 goldens captured by CI across every platform that renders them: android, mac-native, javascript, windows, linux (x64 + arm64) and iOS Metal, plus the tvOS Media360Panorama golden (VRStereoScene skips tvOS, so it has none there). GPU-backed platforms carry the usual tolerance sidecar; linux and the tvOS simulator are bit-exact and need none. Also refreshes the developer-guide 360 image. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ure) Detaching the view after capture was aimed at the iOS Metal DesktopMode mismatch, but that mismatch is a pre-existing flake: DesktopMode and the OrientationLock "landscape" capture differ on the GL iOS build too, where the VR/360 tests do not even run (the late-present race grabs a random wrong form, and OrientationLock is a long-standing ~50% flake on the iOS runners). The detach did not help it, and on the Metal backend it perturbed the GPU state enough to change Media360Panorama's own capture, so its committed golden no longer matched. Restore the tests to their prior form: VRStereoScene via createForm, Media360Panorama as a plain Form that restores portrait after capture. This matches the committed goldens again. The pre-existing iOS DesktopMode / landscape flakes are orthogonal to this change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two iOS Metal fixes, both scoped to Media360Panorama so nothing else shifts: - The iOS Metal DesktopMode screenshot deterministically grabbed this test's full-screen 3D view as a stale black Metal drawable under the late-present race. Detach the peer (removeAll) once the capture is done so no such drawable survives. Only this test detaches - VRStereoScene keeps createForm - so the detach cannot perturb the GPU state that feeds this test's own capture (applying it to both previously shifted Media360Panorama's pixels). - iOS Metal and tvOS render the smooth 360 gradient with more per-pixel variance run-to-run than the default delta=8 GPU tolerance (measured ~20% of pixels shifting 8-16, <0.2% beyond). Widen the per-pixel threshold so gradient noise does not fail the build while a real regression still trips the mismatch percent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On the iOS Metal backends DesktopMode's screenshot deterministically grabbed the Media360Panorama form under the late-present race (it captured that form's frame, not its own), because the immersive tests ran well before it. The GL tests capture their own content with an explicit render, so they are position-independent - it is only the non-GL neighbor whose screenshot can grab a lingering 3D frame. Run VR/360 near the end instead: after DesktopMode and the video screenshot (so neither has a GPU-heavy or 3D neighbor), and before the media round-trip test (so its color-space perturbation cannot reach them). Nothing captures the screen after them, so their landscape state cannot leak either. Their own goldens are unchanged by the move. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ace) With the immersive tests at the suite tail their own iOS Metal captures began reading a previous form's still-current drawable (Media360Panorama captured the OrientationLock form; VRStereoScene shifted) - the same late-present race DesktopMode already guards against. Apply that guard here: override extraSettleBeforeCaptureMillis() to repaint and wait before the capture so the current frame is fully presented first. A no-op cost on the other backends, where these captures were already correct. Also widen the iOS Metal VRStereoScene tolerance to match Media360Panorama, since that backend renders the scene with more per-pixel variance than the default delta=8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lator extraSettle fixed VRStereoScene on every Metal backend and Media360Panorama on Mac Catalyst, but on the iOS/tvOS Metal simulators the panorama capture still read the previous landscape frame (the OrientationLock form). Those simulators upload the 1024x512 panorama texture and present its first frame slower than the plain 3D cubes, especially at the suite tail where the video tests leave the GPU busier. Warm the texture with an early render and wait substantially longer (an extra render plus ~3.5s, on top of the 700ms extraSettle) before capturing, so the textured frame has landed. The panorama is static, so the longer settle does not change the captured pixels on any backend. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
VRStereoScene captures correctly on every Metal backend; Media360Panorama did not on the iOS/tvOS Metal simulators - it read the previous landscape frame (the OrientationLock form) no matter how long the settle. The one structural difference was the capture path: VRStereoScene uses createForm while Media360Panorama used a hand-rolled Form so its completion could detach the peer and restore portrait. At the suite tail neither is needed (DesktopMode runs before it, nothing captures after), so switch to the same createForm + registerReadyCallback + extraSettle path that works for VRStereoScene, keeping only the extra texture-warm render for the 1024x512 upload. The captured pixels are unchanged on the backends that already matched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Media360 screenshot grabbed a stale landscape frame (the OrientationLock form) on the iOS/tvOS Metal simulators: the panorama was a mutable Graphics image, and createTexture(Image) reads it via Image.getRGB(), which on the iOS Metal simulator returns white / drops the first present (the same readback the MutableImageReadbackTest already flags on iOS). So the 360 view never presented a frame and the capture fell back to the last cached landscape content. Compose the panorama pixels directly into an ARGB int[] and hand it to Image.createImage(int[], w, h): texturing an array-backed image only reads the backing array, never a surface, so the first frame presents on iOS exactly like the working Gpu3DTexturedCube (which also uploads its texture from an int[]). Building the pixels by hand also drops the fillArc anti-aliasing, keeping the texture bit-identical across platforms. Media360 goldens are regenerated to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The immutable, array-backed panorama textures slightly brighter on the android (swiftshader) and JavaScript (WebGL) backends than the old mutable-image path did, enough to exceed the 3% tolerance on those two platforms (iOS, mac, Windows and Linux already matched within tolerance). Adopt the new deterministic captures as the baselines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The developer-guide snippet validator requires every code block to be include-backed by a compiled source in docs/demos, not inline. Extract the seven AR/VR snippets into ArSnippets.java / VrSnippets.java (tagged regions) and include them by tag, so the guide code is compiled against the real API on every docs build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…posts (#5357) * Release week 2026-07-10 blog series: ParparVM vs HotSpot + 3 feature posts Parent post (Fri): "How We Beat HotSpot Performance (By Cheating, But Not Like That)" -- the PR #5327 performance story from 4.21x slower than warmed Java 25 to geomean 1.00x parity, with the GC/tagged-Integer deep dives and the PR #5338 synchronized-on-wrapper build check. Daily posts: standalone certificate wizard (PR #5339, Sat), AR/VR support and simulation (PR #5335, Sun), automated store submissions as code incl. Huawei AppGallery plus organization accounts and self-service account deletion (PR #5353, Mon). Includes 1024x512 hero images, post screenshots, and VM/performance vocabulary for the LanguageTool blog accept-list. Prose gate passes; all mermaid diagrams verified against the site's mermaid@10 loader. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Blog series: Oxford commas + numeral percentage for the Vale gate CI runs Vale's Microsoft.OxfordComma and Microsoft.Percentages rules that the local styles cache was missing. Sweep every serial list in the four posts and switch "Ninety percent" to "90%". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Restructure the HotSpot post around architecture Founder feedback pass: add the memory story (table + chart, standalone ParparVM benchmark note, Java 25 AOT fairness note), explain the HotSpot big-block heap vs our malloc/free philosophy and the BiBOP page heap, tease the twists up top, drop the bug-anecdote sections and the warmup/ GraalVM/what-I-got-wrong filler, rework "Going Deeper" around the readable one-file-per-class tradeoff, add a TL;DR, and close on the two-big-PRs slow-week note. Charts split into two clean single-series xycharts (render-verified on mermaid@10 and mermaid.ink for syndication). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
Adds deeper AR/VR support to the framework as two new core packages, with native backends, a simulated backend for the simulator, build-pipeline integration, unit tests and developer guide chapters.
com.codename1.ar(ARKit on iOS, ARCore on Android)gpu.Meshcontent placement, light estimation, image tracking and face tracking.AR.isSupported()/AR.getCapabilities()/AR.open(ARSessionOptions); single activeARSessionowning anARView, anchors and planes.com.codename1.impl.ARImplmirrorsCameraImpl(one impl per session,Display.getARBackend()accessor). Implementation events may arrive on any thread; the core bridge marshals them to the EDT and coalesces high-frequency refinements (anchor/plane updates, camera pose, light) to latest-per-id.ARModelparses via the (new) device-freeGltfLoaderoverloads and hands raw interleaved buffers + a decoded base-color image to the backend.com.codename1.vr(pure core, no platform dependency)VRView: side-by-side stereo rendering of aVRRendereron the existing gpu pipeline, per-eye cameras fromVRCameraRig, head tracking fromHeadTracker(deterministic complementary fusion inOrientationFilter).Media360View: equirectangular panorama viewer with drag and gyroscope look, mono and cardboard-style stereo.TextureSourceis the documented extension point for dynamic content; 360 video and lens distortion are deliberately scoped out (no placeholder API) pending a media-to-texture path and render-to-texture support.Platform backends
CN1AR.{h,m}wraps an ARKitARSessioncomposited through anARSCNView; content buffers becomeSCNGeometry. Gated by a newINCLUDE_CN1_ARdefine (uncommented by the builder only for apps referencing the API), compiled out on tvOS/watchOS, with unconditional stub bridge symbols so non-AR apps link cleanly.com.codename1.impl.android.arpackage (external-OES camera background + lit/textured mesh renderer on aGLSurfaceView); reached reflectively fromAndroidImplementation, deleted by the builder for non-AR apps. The port maven module excludes the package from its own compile (ARCore is not in cn1-binaries) while still shipping it inandroid_port_sources.jar.JavaSEARImplrenders a virtual room (floor/wall planes detected after a realistic delay, mouse-look + WASD camera, deterministic ray-cast hit tests) through the software 3D device, so the full AR loop is debuggable with breakpoints. A new Simulate > AR Simulation window drives tracking failures, light estimates, reference image detection and face toggling. VR/360 run in the simulator through the desktop GL backend with the existing Motion Simulation window feeding the head tracker.Build pipeline
AiDependencyTableentry forcom/codename1/ar/: camera permission +NSCameraUsageDescription, ARCore gradle dependency,android.hardware.camera.arfeature andcom.google.ar.core=optionalmanifest meta-data (newandroidMetaDatatable capability);android.ar.required=trueflips to AR-only. ARKit/SceneKit linked explicitly byIPhoneBuilderand weak-linked on the tv/watch/mac slices. Apps that never reference the package pay no size, permission or store-visibility cost.Test plan
mvn test -Punittests -pl core-unittests: 3830 tests pass, including new suites for the AR session/event bridge/pose math, VR fusion determinism, camera rig, sphere primitive and device-free glTF parsing.mvn test -pl codenameone-maven-plugin: 197 tests pass, including newAiDependencyTableTestcoverage of the AR entry.BUILD SUCCEEDED); the compiled-out stub path is verified clean with a standalone clang syntax check.🤖 Generated with Claude Code