From c80b385258d694c6d877985c3ff2f760388b7a63 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Thu, 15 Oct 2026 15:23:02 +0000 Subject: [PATCH 01/11] Add Synthetic XR Teleoperation desktop preview and examples Signed-off-by: Sameer Tangade --- README.md | 4 + deps/cloudxr/webxr_client/AGENTS.md | 4 + deps/cloudxr/webxr_client/src/App.tsx | 12 +- .../src/syntheticXR/SyntheticXRPanel.tsx | 287 +++++++++++++++ .../src/syntheticXR/controller.test.ts | 120 +++++++ .../src/syntheticXR/controller.ts | 331 ++++++++++++++++++ .../webxr_client/src/syntheticXR/panel.css | 58 +++ .../webxr_client/src/xrInputRecorder.test.ts | 12 +- .../webxr_client/src/xrInputRecorder.ts | 6 +- .../tests/playwright/SyntheticXRTest.spec.js | 87 +++++ docs/source/index.rst | 1 + docs/source/teleop/quick_start.rst | 2 + docs/source/teleop/synthetic_xr.rst | 165 +++++++++ examples/synthetic_xr/LICENSE.md | 201 +++++++++++ examples/synthetic_xr/README.md | 96 +++++ examples/synthetic_xr/VALIDATION.txt | 48 +++ examples/synthetic_xr/pyproject.toml | 25 ++ .../synthetic_xr/__init__.py | 4 + .../synthetic_xr/franka/__init__.py | 4 + .../synthetic_xr/franka/browser_command.py | 147 ++++++++ .../franka/observe_record_demos.py | 300 ++++++++++++++++ .../synthetic_xr/franka/stack_logic.py | 150 ++++++++ .../franka/xr_controller_bridge.py | 168 +++++++++ .../synthetic_xr/launch.py | 47 +++ .../synthetic_xr/replay_once.py | 68 ++++ examples/synthetic_xr/tests/test_examples.py | 94 +++++ 26 files changed, 2431 insertions(+), 10 deletions(-) create mode 100644 deps/cloudxr/webxr_client/src/syntheticXR/SyntheticXRPanel.tsx create mode 100644 deps/cloudxr/webxr_client/src/syntheticXR/controller.test.ts create mode 100644 deps/cloudxr/webxr_client/src/syntheticXR/controller.ts create mode 100644 deps/cloudxr/webxr_client/src/syntheticXR/panel.css create mode 100644 deps/cloudxr/webxr_client/tests/playwright/SyntheticXRTest.spec.js create mode 100644 docs/source/teleop/synthetic_xr.rst create mode 100644 examples/synthetic_xr/LICENSE.md create mode 100644 examples/synthetic_xr/README.md create mode 100644 examples/synthetic_xr/VALIDATION.txt create mode 100644 examples/synthetic_xr/pyproject.toml create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/__init__.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/__init__.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/browser_command.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/launch.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/replay_once.py create mode 100644 examples/synthetic_xr/tests/test_examples.py diff --git a/README.md b/README.md index fed4c9b2c1..c08a22878a 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,10 @@ Our [documentation page](https://nvidia.github.io/IsaacCapture) provides everyth - [How to build from source](https://nvidia.github.io/IsaacCapture/main/getting_started/build_from_source.html) +### Synthetic XR Teleoperation (Preview) + +Try desktop controller input without an XR headset using the [Synthetic XR example](examples/synthetic_xr/README.md). Build this checkout's WebXR client to enable the controls. Host CloudXR requirements still apply. + ### Install & Run Isaac Lab The latest [NVIDIA Isaac Lab](https://github.com/isaac-sim/IsaacLab) release, [3.0.0 EA](https://github.com/isaac-sim/IsaacLab/releases/tag/v3.0.0-EA), includes Isaac Capture, previously released as Isaac Teleop. diff --git a/deps/cloudxr/webxr_client/AGENTS.md b/deps/cloudxr/webxr_client/AGENTS.md index 70817ddc83..7aa6663535 100644 --- a/deps/cloudxr/webxr_client/AGENTS.md +++ b/deps/cloudxr/webxr_client/AGENTS.md @@ -8,3 +8,7 @@ SPDX-License-Identifier: Apache-2.0 Keep `react` and `react-dom` on the same minor release supported by `@react-three/fiber`. Validate dependency-range changes with a clean npm install; do not bypass peer checks with `--force` or `--legacy-peer-deps`. + +Synthetic XR browser checks use the dev server's hot reload. Do not edit or format client files while those checks run: remounting the app invalidates the session and input ownership observations. + +Synthetic examples must start teleoperation through the existing client handler. Do not hard-code a controller ray to an assumed 3D panel position. diff --git a/deps/cloudxr/webxr_client/src/App.tsx b/deps/cloudxr/webxr_client/src/App.tsx index 3da3cfbd80..8a9e21d39e 100644 --- a/deps/cloudxr/webxr_client/src/App.tsx +++ b/deps/cloudxr/webxr_client/src/App.tsx @@ -70,6 +70,7 @@ import { RecorderComponent } from './RecorderComponent'; import { RecorderProvider, useRecorder } from './RecorderContext'; import { SuppressWebGLRendererWhenHeadless } from './SuppressWebGLRendererWhenHeadless'; import { TraceVisualization } from './TraceVisualization'; +import { SyntheticXRFrameObserver, SyntheticXRPanel } from './syntheticXR/SyntheticXRPanel'; import { SystemNotice, formatSystemNotice, @@ -1058,7 +1059,8 @@ function AppContent() { // Calculate panel position from config and memoize it as the vector used in CloudXR3DUI. const controlPanelPositionVector = useMemo( - () => getControlPanelPositionVector(config?.controlPanelPosition ?? 'right', controlPanelLayout), + () => + getControlPanelPositionVector(config?.controlPanelPosition ?? 'right', controlPanelLayout), [config?.controlPanelPosition, controlPanelLayout] ); @@ -1159,6 +1161,13 @@ function AppContent() { return ( <> + + diff --git a/deps/cloudxr/webxr_client/src/syntheticXR/SyntheticXRPanel.tsx b/deps/cloudxr/webxr_client/src/syntheticXR/SyntheticXRPanel.tsx new file mode 100644 index 0000000000..f02aee0985 --- /dev/null +++ b/deps/cloudxr/webxr_client/src/syntheticXR/SyntheticXRPanel.tsx @@ -0,0 +1,287 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { useFrame } from '@react-three/fiber'; + +import { + controllerSweep, + type Hand, + type InputTrace, + type Quaternion, + SyntheticXRController, + type SyntheticDevice, + parseTrace, +} from './controller'; +import './panel.css'; + +declare global { + interface Window { + syntheticXR?: SyntheticXRController; + } +} + +function saveTrace(trace: InputTrace): void { + const url = URL.createObjectURL( + new Blob([JSON.stringify(trace, null, 2)], { type: 'application/json' }) + ); + const a = document.createElement('a'); + a.href = url; + a.download = 'synthetic-xr-input.json'; + a.click(); + setTimeout(() => URL.revokeObjectURL(url), 1000); +} + +export function SyntheticXRPanel({ + ready, + connected, + replaying, + onStartTeleop, + startingOrRunning, +}: { + ready: boolean; + connected: boolean; + replaying: boolean; + onStartTeleop: () => Promise; + startingOrRunning: boolean; +}) { + const [api, setApi] = useState(); + const [hand, setHand] = useState('right'); + const [, refresh] = useState(0); + const [error, setError] = useState(''); + const [trace, setTrace] = useState(); + const enabled = new URLSearchParams(location.search).get('syntheticXR') === '1'; + + useEffect(() => { + if (!enabled || !ready || !window.xrDevice) return; + const controller = new SyntheticXRController(window.xrDevice as unknown as SyntheticDevice); + window.syntheticXR = controller; + const unsubscribe = controller.subscribe(() => refresh(n => n + 1)); + setApi(controller); + const stopOnHidden = () => { + if (document.hidden) controller.stop(); + }; + document.addEventListener('visibilitychange', stopOnHidden); + return () => { + document.removeEventListener('visibilitychange', stopOnHidden); + unsubscribe(); + controller.dispose(); + if (window.syntheticXR === controller) delete window.syntheticXR; + }; + }, [enabled, ready]); + + useEffect(() => { + if (!connected) api?.stop(); + api?.setSuspended(replaying); + }, [api, connected, replaying]); + + if (!enabled) + return ready && window.xrDevice + ? createPortal( + , + document.body + ) + : null; + const act = (fn: () => unknown) => { + try { + fn(); + setError(''); + } catch (e) { + setError(String(e)); + } + }; + const state = api?.getState()[hand]; + const busy = !api || api.isPlaying || replaying; + const rotate = (axis: number, delta: number) => { + if (!api || !state) return; + const r = [0, 0, 0, Math.cos(delta / 2)]; + r[axis] = Math.sin(delta / 2); + const [x, y, z, w] = state.quaternion; + const [a, b, c, d] = r; + const q: Quaternion = [ + d * x + a * w + b * z - c * y, + d * y - a * z + b * w + c * x, + d * z + a * y - b * x + c * w, + d * w - a * x - b * y - c * z, + ]; + api.setController(hand, { quaternion: q }); + }; + return createPortal( +
+
+ + Synthetic XR Teleoperation Preview + + {!api ? ( +

+ {ready + ? 'Native XR is active. Desktop controls require the IWER fallback.' + : 'Waiting for the desktop XR runtime…'} +

+ ) : ( + <> +

+ {connected ? 'Connected' : 'Connect the WebXR client to send input'} · {api.status} +

+ + +
+ Pose in metres · IWER local space + {(['X', 'Y', 'Z'] as const).map((name, index) => ( + + ))} + + + +
+
+ + + + + +
+ {replaying &&

Stop browser recording replay to use synthetic controllers.

} +

+ Virtual controller input through IWER. Stop releases grip and trigger; use the + application’s stop control to stop the robot task. +

+ + )} + {error &&

{error}

} +
+
, + document.body + ); +} + +export function SyntheticXRFrameObserver() { + useFrame((state, _delta, frame) => { + const reference = state.gl.xr.getReferenceSpace(); + if (frame && reference) window.syntheticXR?.observeFrame(frame, reference); + }); + return null; +} diff --git a/deps/cloudxr/webxr_client/src/syntheticXR/controller.test.ts b/deps/cloudxr/webxr_client/src/syntheticXR/controller.test.ts new file mode 100644 index 0000000000..ba9fc6949c --- /dev/null +++ b/deps/cloudxr/webxr_client/src/syntheticXR/controller.test.ts @@ -0,0 +1,120 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { + controllerSweep, + initialInput, + parseTrace, + SyntheticXRController, + type SyntheticDevice, +} from './controller'; + +function device(): SyntheticDevice { + const controller = () => ({ + position: { set: jest.fn() }, + quaternion: { set: jest.fn() }, + updateButtonValue: jest.fn(), + }); + return { + controlMode: 'manual', + position: { set: jest.fn() }, + quaternion: { set: jest.fn() }, + controllers: { left: controller(), right: controller() }, + notifyStateChange: jest.fn(), + }; +} + +describe('Synthetic controller input contract', () => { + beforeEach(() => jest.useFakeTimers()); + afterEach(() => jest.useRealTimers()); + test('maps grip to squeeze and supplies quaternion without changing convention', () => { + const d = device(); + const api = new SyntheticXRController(d); + api.setController('right', { + position: [1, 2, -1], + quaternion: [0, 1, 0, 0], + trigger: 0.7, + grip: 1, + }); + expect(d.controllers.right.position.set).toHaveBeenLastCalledWith(1, 2, -1); + expect(d.controllers.right.quaternion.set).toHaveBeenLastCalledWith(0, 1, 0, 0); + expect(d.controllers.right.updateButtonValue).toHaveBeenCalledWith('squeeze', 1); + expect(d.controllers.right.updateButtonValue).toHaveBeenCalledWith('trigger', 0.7); + api.dispose(); + expect(d.controlMode).toBe('manual'); + }); + test.each([ + { position: [NaN, 0, 0] }, + { position: [11, 0, 0] }, + { quaternion: [0, 0, 0, 0] }, + { trigger: 2 }, + { grip: -1 }, + ])('invalid updates are atomic: %j', update => { + const d = device(); + const api = new SyntheticXRController(d); + const old = api.getState(); + const calls = (d.controllers.right.position.set as jest.Mock).mock.calls.length; + expect(() => api.setController('right', update as never)).toThrow(); + expect(api.getState()).toEqual(old); + expect(d.controllers.right.position.set).toHaveBeenCalledTimes(calls); + api.dispose(); + }); + test('single replay completes, releases buttons and keeps final pose', async () => { + const api = new SyntheticXRController(device()); + const trace = controllerSweep(); + trace.frames[180].state.right.trigger = 1; + const done = api.play(trace); + expect(() => api.setHead({ position: [0, 1, 0] })).toThrow('Stop playback'); + jest.advanceTimersByTime(9050); + await done; + expect(api.isPlaying).toBe(false); + expect(api.getState().right.trigger).toBe(0); + expect(api.getState().right.position).toEqual(trace.frames[180].state.right.position); + api.dispose(); + }); + test('loop restarts and explicit stop cancels scheduled input', async () => { + const api = new SyntheticXRController(device()); + const done = api.play(controllerSweep(), { loop: true }); + jest.advanceTimersByTime(9400); + expect(api.isPlaying).toBe(true); + api.stop(); + await done; + const stopped = api.getState(); + jest.advanceTimersByTime(20000); + expect(api.getState()).toEqual(stopped); + expect(stopped.right.grip).toBe(0); + api.dispose(); + }); + test('recording captures input and final hold; exported state cannot mutate device', () => { + const api = new SyntheticXRController(device()); + api.record(); + jest.advanceTimersByTime(100); + api.setController('left', { trigger: 0.5 }); + jest.advanceTimersByTime(100); + const trace = api.finishRecording(); + expect(trace.frames.map(f => f.timeMs)).toEqual([0, 100, 200]); + expect(trace.frames[1].state.left.trigger).toBe(0.5); + trace.frames[1].state.left.trigger = 1; + expect(api.getState().left.trigger).toBe(0.5); + api.dispose(); + }); + test('rejects malformed traces before starting or changing input', () => { + const trace = controllerSweep(); + trace.frames[1].timeMs = 0; + const api = new SyntheticXRController(device()); + expect(() => api.play(trace)).toThrow('strictly increase'); + expect(api.getState()).toEqual(initialInput()); + expect(() => parseTrace({ version: 1, frames: [] })).toThrow(); + api.dispose(); + }); + test('browser replay suspends script input; disposal makes retained handles unusable', () => { + const api = new SyntheticXRController(device()); + api.setSuspended(true); + expect(() => api.setController('right', { trigger: 1 })).toThrow('owns tracking'); + api.setSuspended(false); + api.setController('right', { trigger: 1 }); + api.dispose(); + expect(() => api.reset()).toThrow('disposed'); + }); +}); diff --git a/deps/cloudxr/webxr_client/src/syntheticXR/controller.ts b/deps/cloudxr/webxr_client/src/syntheticXR/controller.ts new file mode 100644 index 0000000000..dedc4acbe7 --- /dev/null +++ b/deps/cloudxr/webxr_client/src/syntheticXR/controller.ts @@ -0,0 +1,331 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export type Hand = 'left' | 'right'; +export type Vector3 = [number, number, number]; +export type Quaternion = [number, number, number, number]; +export type Pose = { position: Vector3; quaternion: Quaternion }; +export type ControllerState = Pose & { trigger: number; grip: number }; +export type InputState = { head: Pose; left: ControllerState; right: ControllerState }; +export type InputTrace = { + version: 1; + referenceSpace: 'iwer-local'; + frames: Array<{ timeMs: number; state: InputState }>; +}; + +/** Structural interface keeps generated inputs independent of the IWER distribution. */ +export interface SyntheticDevice { + controlMode: string; + notifyStateChange?: () => void; + position: { set: (...v: Vector3) => unknown }; + quaternion: { set: (...q: Quaternion) => unknown }; + controllers: Record< + Hand, + { + position: { set: (...v: Vector3) => unknown }; + quaternion: { set: (...q: Quaternion) => unknown }; + updateButtonValue: (button: string, value: number) => void; + } + >; +} + +const copy = (value: T): T => JSON.parse(JSON.stringify(value)); +export function initialInput(): InputState { + return { + head: { position: [0, 1.6, 0], quaternion: [0, 0, 0, 1] }, + left: { position: [-0.25, 1.3, -0.4], quaternion: [0, 0, 0, 1], trigger: 0, grip: 0 }, + right: { position: [0.25, 1.3, -0.4], quaternion: [0, 0, 0, 1], trigger: 0, grip: 0 }, + }; +} + +function validatePose(pose: Pose): void { + if ( + !pose || + !Array.isArray(pose.position) || + pose.position.length !== 3 || + !pose.position.every(n => Number.isFinite(n) && Math.abs(n) <= 10) + ) { + throw new Error('Position must contain three finite metre values within ±10 m.'); + } + if ( + !Array.isArray(pose.quaternion) || + pose.quaternion.length !== 4 || + !pose.quaternion.every(Number.isFinite) || + Math.abs(Math.hypot(...pose.quaternion) - 1) > 0.001 + ) { + throw new Error('Quaternion must be normalized, in x/y/z/w order.'); + } +} + +function validateState(state: InputState): void { + if (!state) throw new Error('Missing input state.'); + validatePose(state.head); + for (const hand of ['left', 'right'] as const) { + validatePose(state[hand]); + for (const value of [state[hand].trigger, state[hand].grip]) { + if (!Number.isFinite(value) || value < 0 || value > 1) { + throw new Error('Trigger and grip must be in [0, 1].'); + } + } + } +} + +export function parseTrace(value: unknown): InputTrace { + const trace = value as InputTrace; + if ( + !trace || + trace.version !== 1 || + trace.referenceSpace !== 'iwer-local' || + !Array.isArray(trace.frames) || + trace.frames.length < 2 || + trace.frames.length > 36000 + ) { + throw new Error('Expected a version 1 iwer-local trace with 2–36000 frames.'); + } + let previous = -1; + for (const frame of trace.frames) { + if ( + !frame || + !Number.isFinite(frame.timeMs) || + frame.timeMs < 0 || + frame.timeMs <= previous || + frame.timeMs > 600000 + ) { + throw new Error('Trace times must strictly increase within ten minutes.'); + } + validateState(frame.state); + previous = frame.timeMs; + } + if (trace.frames[0].timeMs !== 0) throw new Error('Trace must start at zero.'); + return copy(trace); +} + +export class SyntheticXRController { + readonly version = 1; + private state = initialInput(); + private timer: ReturnType | undefined; + private originalMode: string; + private recording: InputTrace | undefined; + private recordStart = 0; + private disposed = false; + private listeners = new Set<() => void>(); + private finishPlayback: (() => void) | undefined; + private suspended = false; + lastFrame: { + time: number; + sources: Array<{ hand: string; aim: Pose | null; grip: Pose | null; buttons: number[] }>; + } | null = null; + status = 'Ready'; + + constructor(private device: SyntheticDevice) { + this.originalMode = device.controlMode; + device.controlMode = 'programmatic'; + this.apply(this.state); + } + + getState(): InputState { + return copy(this.state); + } + get isPlaying(): boolean { + return this.timer !== undefined; + } + get isRecording(): boolean { + return this.recording !== undefined; + } + subscribe(listener: () => void): () => void { + this.listeners.add(listener); + return () => { + this.listeners.delete(listener); + }; + } + + private changed(): void { + this.listeners.forEach(fn => fn()); + } + private assertActive(): void { + if (this.disposed) throw new Error('Synthetic XR controller has been disposed.'); + } + private assertManual(): void { + this.assertActive(); + if (this.suspended) throw new Error('Browser replay owns tracking input.'); + if (this.isPlaying) throw new Error('Stop playback before changing input.'); + } + private apply(state: InputState): void { + this.assertActive(); + validateState(state); + this.device.position.set(...state.head.position); + this.device.quaternion.set(...state.head.quaternion); + for (const hand of ['left', 'right'] as const) { + const target = this.device.controllers[hand]; + target.position.set(...state[hand].position); + target.quaternion.set(...state[hand].quaternion); + target.updateButtonValue('trigger', state[hand].trigger); + target.updateButtonValue('squeeze', state[hand].grip); + } + this.state = copy(state); + this.device.notifyStateChange?.(); + this.changed(); + } + + setSuspended(value: boolean): void { + if (value && !this.suspended) this.stop(); + this.suspended = value; + } + + observeFrame(frame: XRFrame, reference: XRReferenceSpace): void { + const pose = (value: XRPose | null | undefined): Pose | null => + value + ? { + position: [ + value.transform.position.x, + value.transform.position.y, + value.transform.position.z, + ], + quaternion: [ + value.transform.orientation.x, + value.transform.orientation.y, + value.transform.orientation.z, + value.transform.orientation.w, + ], + } + : null; + this.lastFrame = { + time: performance.now(), + sources: Array.from(frame.session.inputSources).map(source => ({ + hand: source.handedness, + aim: pose(frame.getPose(source.targetRaySpace, reference)), + grip: source.gripSpace ? pose(frame.getPose(source.gripSpace, reference)) : null, + buttons: Array.from(source.gamepad?.buttons ?? [], button => button.value), + })), + }; + } + + setController(hand: Hand, update: Partial): void { + this.assertManual(); + if (hand !== 'left' && hand !== 'right') throw new Error('Choose left or right.'); + this.apply({ ...this.state, [hand]: { ...this.state[hand], ...update } }); + this.capture(); + } + setHead(update: Partial): void { + this.assertManual(); + this.apply({ ...this.state, head: { ...this.state.head, ...update } }); + this.capture(); + } + reset(): void { + this.assertManual(); + this.apply(initialInput()); + this.capture(); + } + + record(): void { + this.assertManual(); + if (this.isRecording) throw new Error('Already recording.'); + this.recordStart = performance.now(); + this.recording = { + version: 1, + referenceSpace: 'iwer-local', + frames: [{ timeMs: 0, state: this.getState() }], + }; + this.status = 'Recording input'; + this.changed(); + } + private capture(): void { + if (!this.recording) return; + const frames = this.recording.frames; + const timeMs = performance.now() - this.recordStart; + if (timeMs > 600000 || frames.length >= 35999) { + this.status = 'Recording limit reached — export trace'; + this.changed(); + return; + } + if (timeMs > frames[frames.length - 1].timeMs) frames.push({ timeMs, state: this.getState() }); + } + finishRecording(): InputTrace { + this.assertActive(); + if (!this.recording) throw new Error('No recording in progress.'); + this.capture(); + if (this.recording.frames.length < 2) throw new Error('Record for longer before exporting.'); + const result = parseTrace(this.recording); + this.recording = undefined; + this.status = 'Input trace saved'; + this.changed(); + return result; + } + + play(value: unknown, options: { loop?: boolean } = {}): Promise { + this.assertManual(); + if (this.isRecording) throw new Error('Finish recording before playback.'); + const trace = parseTrace(value); + const started = performance.now(); + const duration = trace.frames[trace.frames.length - 1].timeMs; + this.apply(trace.frames[0].state); + this.status = options.loop ? 'Playing loop' : 'Playing once'; + const done = new Promise(resolve => { + this.finishPlayback = resolve; + }); + this.timer = setInterval(() => { + const elapsed = performance.now() - started; + if (elapsed >= duration && !options.loop) { + this.apply(trace.frames[trace.frames.length - 1].state); + this.stop(); + this.status = 'Playback complete — buttons released'; + this.changed(); + return; + } + const time = options.loop ? elapsed % duration : elapsed; + let index = 0; + while (index + 1 < trace.frames.length && trace.frames[index + 1].timeMs <= time) index++; + this.apply(trace.frames[index].state); + }, 16); + this.changed(); + return done; + } + + stop(): void { + this.assertActive(); + const hadInput = + this.isPlaying || + this.state.left.grip || + this.state.left.trigger || + this.state.right.grip || + this.state.right.trigger; + clearInterval(this.timer); + this.timer = undefined; + this.apply({ + ...this.state, + left: { ...this.state.left, grip: 0, trigger: 0 }, + right: { ...this.state.right, grip: 0, trigger: 0 }, + }); + this.capture(); + // Repeated disconnect notifications must not erase a completed replay result. + if (hadInput) this.status = 'Stopped — buttons released'; + this.finishPlayback?.(); + this.finishPlayback = undefined; + this.changed(); + } + dispose(): void { + if (this.disposed) return; + this.stop(); + this.device.controlMode = this.originalMode; + this.listeners.clear(); + this.disposed = true; + } +} + +/** A generated sequence, not a captured human demonstration. */ +export function controllerSweep(): InputTrace { + const frames: InputTrace['frames'] = []; + for (let i = 0; i <= 180; i++) { + const state = initialInput(); + const phase = (i / 180) * Math.PI * 2; + state.right.position[0] += 0.12 * Math.sin(phase); + state.right.position[1] += 0.08 * (1 - Math.cos(phase)); + state.left.position[2] -= 0.06 * Math.sin(phase); + state.right.grip = i > 0 && i < 180 ? 1 : 0; + state.right.trigger = i >= 60 && i < 120 ? 1 : 0; + frames.push({ timeMs: i * 50, state }); + } + return { version: 1, referenceSpace: 'iwer-local', frames }; +} diff --git a/deps/cloudxr/webxr_client/src/syntheticXR/panel.css b/deps/cloudxr/webxr_client/src/syntheticXR/panel.css new file mode 100644 index 0000000000..6e161a4c77 --- /dev/null +++ b/deps/cloudxr/webxr_client/src/syntheticXR/panel.css @@ -0,0 +1,58 @@ +/* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 */ +.synthetic-xr { + position: fixed; + right: 16px; + bottom: 16px; + z-index: 10000; + width: 330px; + max-width: calc(100vw - 32px); + max-height: 85vh; + overflow: auto; + padding: 14px; + border: 1px solid #76b900; + border-radius: 8px; + background: #172019; + color: #f4f7f2; + font: 14px/1.4 sans-serif; +} +.synthetic-xr summary { + cursor: pointer; + font-weight: bold; +} +.synthetic-xr small { + color: #b6dc7b; +} +.synthetic-xr label { + display: flex; + align-items: center; + gap: 8px; + margin: 7px 0; +} +.synthetic-xr input[type='number'] { + width: 85px; +} +.synthetic-xr input[type='file'] { + width: 210px; +} +.synthetic-xr button, +.synthetic-xr select { + padding: 5px 8px; +} +.synthetic-xr-actions { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 10px; +} +.synthetic-xr-stop { + background: #f6d58a; + color: #171717; +} +.synthetic-xr-note { + font-size: 12px; + color: #d0daca; +} +.synthetic-xr [role='alert'] { + color: #ffb9a9; +} diff --git a/deps/cloudxr/webxr_client/src/xrInputRecorder.test.ts b/deps/cloudxr/webxr_client/src/xrInputRecorder.test.ts index ca967ab17d..2e97ca475b 100644 --- a/deps/cloudxr/webxr_client/src/xrInputRecorder.test.ts +++ b/deps/cloudxr/webxr_client/src/xrInputRecorder.test.ts @@ -45,7 +45,7 @@ function pose(x: number, y = 0, z = 0, orientation: DOMPointInit = { w: 1 }): XR emulatedPosition: false, linearVelocity: null, angularVelocity: null, - } as XRPose; + } as unknown as XRPose; } function jointPose(x: number, radius = 0.01): XRJointPose { @@ -56,7 +56,7 @@ function gamepad(axis: number): Gamepad { return { axes: [axis], buttons: [{ value: axis, pressed: axis > 0, touched: true }], - } as Gamepad; + } as unknown as Gamepad; } type PoseResolver = (space: XRSpace, baseSpace: XRSpace) => XRPose | null; @@ -68,7 +68,7 @@ function makeFrame( getJointPose: JointResolver = () => null, predictedDisplayTime = 0 ): XRFrame { - const session = { inputSources } as XRSession; + const session = { inputSources } as unknown as XRSession; return { session, predictedDisplayTime, @@ -360,8 +360,8 @@ describe('scoped CloudXR replay frame', () => { expect(adapted.session.inputSources).toBe(replaySources); expect(replaySources[0].gamepad).toBe(replaySources[0].gamepad); expect(replaySources[0].gamepad?.axes).toEqual([3]); - expect(adapted.getJointPose(wrist, cloudSpace)?.transform.position.x).toBe(15); - expect(adapted.getJointPose(wrist, cloudSpace)?.radius).toBe(0.02); + expect(adapted.getJointPose!(wrist, cloudSpace)?.transform.position.x).toBe(15); + expect(adapted.getJointPose!(wrist, cloudSpace)?.radius).toBe(0.02); }); test('delegates unknown spaces and joints to the real frame', () => { @@ -378,7 +378,7 @@ describe('scoped CloudXR replay frame', () => { const adapted = recorder.adaptTrackingFrame(frame); expect(adapted.getPose(unknownSpace, sceneSpace)?.transform.position.x).toBe(7); - expect(adapted.getJointPose(unknownJoint, sceneSpace)?.transform.position.x).toBe(8); + expect(adapted.getJointPose!(unknownJoint, sceneSpace)?.transform.position.x).toBe(8); }); }); diff --git a/deps/cloudxr/webxr_client/src/xrInputRecorder.ts b/deps/cloudxr/webxr_client/src/xrInputRecorder.ts index 8838d38ba5..f6861f2892 100644 --- a/deps/cloudxr/webxr_client/src/xrInputRecorder.ts +++ b/deps/cloudxr/webxr_client/src/xrInputRecorder.ts @@ -132,8 +132,8 @@ function makePose(pose: PoseData): XRPose { { x: pose.ox, y: pose.oy, z: pose.oz, w: pose.ow } ), emulatedPosition: true, - linearVelocity: null, - angularVelocity: null, + linearVelocity: undefined, + angularVelocity: undefined, } as XRPose; } @@ -622,7 +622,7 @@ export class XRInputRecorder { return transformed ? makeJointPose(transformed) : undefined; } } - return frame.getJointPose(joint, baseSpace) ?? undefined; + return frame.getJointPose?.(joint, baseSpace) ?? undefined; } private _poseInBase( diff --git a/deps/cloudxr/webxr_client/tests/playwright/SyntheticXRTest.spec.js b/deps/cloudxr/webxr_client/tests/playwright/SyntheticXRTest.spec.js new file mode 100644 index 0000000000..ba07cdb15f --- /dev/null +++ b/deps/cloudxr/webxr_client/tests/playwright/SyntheticXRTest.spec.js @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +const { test, expect } = require('@playwright/test'); + +// Real IWER and production UI; CloudXR transport is mocked in this browser-only suite. +test('synthetic input reaches real WebXR poses and buttons; stop releases input', async ({ + page, +}) => { + await page.goto('http://localhost:8082/?syntheticXR=1&immersiveMode=vr'); + await page.waitForFunction(() => Boolean(window.syntheticXR)); + await page.click('#startButton'); + await page.waitForFunction(() => window.syntheticXR.lastFrame?.sources.length === 2); + await page.getByRole('button', { name: 'Start teleoperation', exact: true }).click(); + await expect( + page.getByRole('button', { name: 'Teleoperation started', exact: true }) + ).toBeDisabled(); + await page.getByLabel('X position').fill('0.4'); + await page.getByLabel('Grip / clutch').check(); + await page.evaluate(() => window.syntheticXR.setController('right', { trigger: 0.8 })); + await expect + .poll(() => + page.evaluate(() => { + const right = window.syntheticXR.lastFrame.sources.find(s => s.hand === 'right'); + return { + x: Math.round(right.aim.position[0] * 100), + trigger: right.buttons[0], + grip: right.buttons[1], + }; + }) + ) + .toEqual({ x: 40, trigger: 0.8, grip: 1 }); + await page.getByRole('button', { name: 'Stop input', exact: true }).click(); + await expect + .poll(() => + page.evaluate(() => + window.syntheticXR.lastFrame.sources.find(s => s.hand === 'right').buttons.slice(0, 2) + ) + ) + .toEqual([0, 0]); +}); + +test('generated sweep completes once and an exported trace can be imported', async ({ page }) => { + await page.goto('http://localhost:8082/?syntheticXR=1&immersiveMode=vr'); + await page.waitForFunction(() => Boolean(window.syntheticXR)); + await page.click('#startButton'); + const run = page.getByRole('button', { name: 'Run controller sweep' }); + await expect(run).toBeEnabled(); + await run.click(); + await expect + .poll(() => page.evaluate(() => window.syntheticXR.status), { timeout: 15000 }) + .toContain('Playback complete'); + await page.getByRole('button', { name: 'Record input', exact: true }).click(); + await page.getByLabel('X position').fill('0.45'); + const downloadPromise = page.waitForEvent('download'); + await page.getByRole('button', { name: 'Save input trace' }).click(); + const download = await downloadPromise; + const path = await download.path(); + await page.getByLabel('Load synthetic trace').setInputFiles(path); + await page.getByRole('button', { name: 'Replay once', exact: true }).click(); + await expect + .poll(() => page.evaluate(() => window.syntheticXR.status)) + .toContain('Playback complete'); +}); + +test('ordinary desktop load does not install the synthetic control API', async ({ page }) => { + await page.goto('http://localhost:8082/'); + await page.getByRole('button', { name: 'Open Synthetic XR controls' }).waitFor(); + expect(await page.evaluate(() => window.syntheticXR)).toBeUndefined(); +}); + +test('native immersive capability never installs or overrides synthetic XR', async ({ page }) => { + await page.addInitScript(() => { + window.nativeXRMarker = Object.assign(new EventTarget(), { + isSessionSupported: async () => true, + }); + Object.defineProperty(navigator, 'xr', { value: window.nativeXRMarker, configurable: true }); + }); + await page.goto('http://localhost:8082/?syntheticXR=1'); + await expect(page.getByText('Native XR is active.', { exact: false })).toBeVisible(); + expect( + await page.evaluate( + () => navigator.xr === window.nativeXRMarker && !window.syntheticXR && !window.xrDevice + ) + ).toBe(true); +}); diff --git a/docs/source/index.rst b/docs/source/index.rst index 030717b500..f8ea1b33bc 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -54,6 +54,7 @@ Table of Contents :caption: Isaac Teleop teleop/quick_start + teleop/synthetic_xr teleop/teleop_session teleop/control_state_machine diff --git a/docs/source/teleop/quick_start.rst b/docs/source/teleop/quick_start.rst index d4b866ef53..ef53e319bb 100644 --- a/docs/source/teleop/quick_start.rst +++ b/docs/source/teleop/quick_start.rst @@ -8,6 +8,8 @@ This guide walks through running a teleoperation session with an XR headset usin end you will have the retargeting pipeline processing live hand/controller data and printing gripper commands to the terminal. +No headset? See :doc:`synthetic_xr` for desktop controller inputs and examples. + .. contents:: Steps :local: :depth: 1 diff --git a/docs/source/teleop/synthetic_xr.rst b/docs/source/teleop/synthetic_xr.rst new file mode 100644 index 0000000000..54d05b5666 --- /dev/null +++ b/docs/source/teleop/synthetic_xr.rst @@ -0,0 +1,165 @@ +.. SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +.. SPDX-License-Identifier: Apache-2.0 + +Synthetic XR Teleoperation +========================== + +Control virtual XR inputs from your desktop browser, without a physical headset. +Use interactive controls or generated controller trajectories to develop Isaac +Teleop applications, record inputs and reproduce behavior. This preview uses the +existing IWER fallback in the WebXR client and the normal CloudXR transport. + +Requirements +------------ + +Complete the host setup in :doc:`quick_start`. The same CloudXR runtime, GPU, +network and certificate requirements apply. Isaac Lab is needed only for the +Franka simulation example. Desktop operation requires neither ADB nor a headset. +Native headset sessions retain their own XR runtime; this feature does not +replace it with IWER. + +Build the client from this checkout following +:doc:`/development/build_from_source/webxr`. Start the service with that build: + +.. code-block:: bash + + export TELEOP_WEB_CLIENT_STATIC_DIR="$PWD/deps/cloudxr/webxr_client/build" + python -m isaaccapture.cloudxr.service start --host-client + +Use the client URL printed by the service. Open **Synthetic XR controls** to +reload with the panel enabled, or add ``syntheticXR=1`` to the URL query. +For an optional desktop launcher, install the example from this checkout: + +.. code-block:: bash + + python -m pip install -e ./examples/synthetic_xr + synthetic-xr --client-url 'https://HOST:PORT/client/' + +Replace the URL with the service's actual URL, keeping its query parameters and +application fragment. The launcher preserves them. It opens the client; it does +not start another runtime. For unattended hub setup without ADB, see +:doc:`/references/oob_teleop_control` and ``TELEOP_OOB_HUB_ONLY=1``. + +Move controllers +---------------- + +#. Connect the WebXR client and start a native input viewer or robot application. +#. Select the **Left** or **Right** controller in the Synthetic XR panel. +#. Change X/Y/Z in metres or rotate around an axis using the ten-degree buttons. +#. Use **Grip / clutch** and **Trigger** according to the application's mapping. +#. Use **Stop input** to cancel synthetic playback and release both buttons. + +**Reset virtual poses** resets input poses, not the simulator. **Stop input** +is an input stop, not an application emergency stop: use the robot application's +stop control to stop its task. Switching to a hidden browser tab also stops +synthetic playback and releases buttons. + +Controller poses use IWER local coordinates: X right, Y up, Z backward, with +quaternions in x/y/z/w order. They describe the controller's target-ray pose; +its grip pose additionally includes the selected IWER profile's grip offset. +The browser produces poses and buttons; host retargeting and a simulation task's +IK solver determine robot joint motion. + +Run and record a generated sequence +----------------------------------- + +**Run controller sweep** generates a nine-second sequence with separate left +and right movement, a grip interval and a trigger interval. It runs once and +releases buttons on completion. It requires no captured manual demonstration. + +Use **Record input** while moving the virtual controllers, then **Save input +trace**. Load that JSON with **Load trace**, then select **Replay once**. +This trace records complete synthetic input states at command times; playback +holds each state until the next timestamp. It is distinct from the client's +existing XR-frame recording JSON and from native MCAP. + +Only one input producer may own synthetic control. Manual/API pose changes are +rejected while a trace is playing. The client suspends synthetic controls while +its existing browser-recording replay owns the CloudXR tracking adapter. + +Native MCAP record and single replay +------------------------------------ + +Install the existing :doc:`/references/mcap_record_replay` example. With the +native runtime and desktop client connected, record a controller sequence: + +.. code-block:: bash + + python -m isaaccapture_examples.mcap_record_replay.record_controller 15 controllers.mcap + +Run the sweep during that recording. Then replay the MCAP once using this +package's wrapper around the native example's replay pipeline: + +.. code-block:: bash + + python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap + +For an Isaac Teleop 1.5 installation, add ``--legacy-example-dir`` pointing to +that SDK checkout's ``examples/mcap_record_replay/python`` directory. This +explicitly selects its native replay implementation. The default command +requires the current Isaac Capture example package. The native example polls at +60 Hz; a lower-rate recording may exhaust its payloads before its duration timer +expires and then show invalid inputs. Payload equality and order are verified; +original sample timing is not part of this preview's qualification. + +Open the printed viewer URL. The wrapper plays one sequence, holds the final +view for five seconds and closes. Native MCAP replay needs no live XR session. +The main MCAP example retains its existing repeated-playback behavior. + +Script your own input +--------------------- + +In a desktop client opened with ``syntheticXR=1``, the preview API is available +as ``window.syntheticXR``. For example, in browser developer tools: + +.. code-block:: javascript + + const input = window.syntheticXR; + input.setController('right', { + position: [0.3, 1.3, -0.4], + quaternion: [0, 0, 0, 1], + grip: 1, + trigger: 0.5, + }); + input.stop(); + +The reusable implementation is +:code-file:`src/syntheticXR/controller.ts `. +``setHead``, ``getState``, ``record``, ``finishRecording``, ``play`` and ``stop`` +provide input generation and reproduction. ``play(trace)`` runs once; +``play(trace, {loop: true})`` repeats until stopped. ``lastFrame`` exposes observed +WebXR aim/grip poses and gamepad values for debugging, not host acknowledgements. + +Inputs must be finite, positions within ten metres per axis, normalized +quaternions and button values in [0, 1]. Trace version 1 uses +``referenceSpace: 'iwer-local'`` and strictly increasing ``timeMs`` values +starting at zero, with complete ``head``, ``left`` and ``right`` state in each +frame. Traces are limited to ten minutes and 36,000 frames. Schedule button +transitions far enough apart to be observed by the application's frame loop. + +Franka simulation example +------------------------- + +The :code-file:`Franka walkthrough ` runs the +native Lab recording script with read-only scene observations. Its +``stack_logic`` computes targets and submits them to the same browser API; +robot actuation still passes through WebXR, CloudXR, retargeting and native IK. +It uses the Lab task's ground truth rather than a learned policy or perception. +The observer depends on the documented Lab integration hooks listed in the +walkthrough; verify compatibility before using another Lab revision. + +Scope and troubleshooting +------------------------- + +* **Controls unavailable:** check that desktop IWER loaded and the URL has + ``syntheticXR=1``. Native immersive XR sessions intentionally keep their input. +* **No movement:** verify connection, fresh native poses, clutch and application + start state. Moving a controller is not itself a robot task success signal. +* **Replay rejected:** synthetic traces, browser recordings and MCAP are separate + formats. Use the corresponding replay path. +* **No WebGL:** use a browser/driver combination that supports WebGL2. Do not + treat an unavailable renderer as a successful transport test. + +This controller preview does not emulate physical tracking accuracy, haptics, +Pico/Quest device behavior or glove protocols. Hand/body adapters are outside +this release. Scripted simulation data is synthetic, not a human demonstration. diff --git a/examples/synthetic_xr/LICENSE.md b/examples/synthetic_xr/LICENSE.md new file mode 100644 index 0000000000..1a89b9054d --- /dev/null +++ b/examples/synthetic_xr/LICENSE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 NVIDIA Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/synthetic_xr/README.md b/examples/synthetic_xr/README.md new file mode 100644 index 0000000000..5ce010e1d1 --- /dev/null +++ b/examples/synthetic_xr/README.md @@ -0,0 +1,96 @@ + + +# Synthetic XR Teleoperation — Preview + +Use virtual controllers from the existing WebXR client. No physical headset or manual demonstration is required. CloudXR host requirements still apply. + +## Desktop controls + +Build this checkout's WebXR client using `docs/source/development/build_from_source/webxr.rst`, then serve its `build/` directory through the existing CloudXR service's `--host-client` option and `TELEOP_WEB_CLIENT_STATIC_DIR`. Do not use the public prebuilt client for this preview; it does not contain this branch's controls. + +From the repository root: + +```bash +python -m pip install -e ./examples/synthetic_xr +synthetic-xr --client-url 'https://HOST:PORT/client/' +``` + +Replace the URL with the service's printed URL, retaining its query and application fragment. Connect, open the synthetic input panel and select a controller. For applications that require a start command, use **Start teleoperation**, which invokes the existing client handler. Change position or orientation, engage Grip / clutch, or change Trigger. Run controller sweep generates a nine-second sequence and releases both buttons when complete. + +Record input and Save input trace export a synthetic command trace. Load trace and Replay once reproduce it through IWER. The existing browser recorder and native MCAP use different formats and injection boundaries. See `docs/source/teleop/synthetic_xr.rst` for the API and trace contract. + +## Native controller MCAP + +Install this checkout's existing `examples/mcap_record_replay` package first. Start its controller recording with a connected desktop client and run the sweep during capture: + +```bash +python -m isaaccapture_examples.mcap_record_replay.record_controller 15 controllers.mcap +python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap +``` + +The second command opens a native replay viewer on loopback port 8090, plays once, leaves the viewer open for five seconds, then closes. No live XR session is required during replay. + +For the supported Isaac Teleop 1.5 compatibility stack, select the installed SDK's original example explicitly: + +```bash +python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap \ + --legacy-example-dir /absolute/path/to/IsaacCapture/examples/mcap_record_replay/python +``` + +This invokes that version's native replay pipeline; it does not emulate MCAP decoding or alias the newer SDK to the older one. Its replay loop polls at 60 Hz: a lower-rate recording can finish yielding payloads before the duration timer expires, leaving an invalid/empty view until exit. This preview verifies payload order and values, not reproduction of original sample timing. + +## Franka stacking + +This integration adds read-only observation hooks around Lab's `record_demos.py`. It does not replace the native task pipeline or write joint actions. `stack_logic` reads cube and tool poses, converts targets into XR controller poses and grip/trigger input, and waits for matching native actions. + +The integration is built for the prepared Lab revision `cb9628bee016c532feb5fc36c3b012fe707bed1b`, its `IsaacContrib-Stack-Cube-Franka-IK-Abs` task, CloudXR 6.3.0 and the installed Isaac Teleop 1.5 backend. The new client is based on Isaac Capture `e07a2cc8c3fcbc8d0aac9f9bdc7919a792983e44`. These component identities are distinct; a new-client check is not a fresh validation of every current SDK backend. See `VALIDATION.txt` for the measured result and limits before treating another combination as supported. + +In the configured Lab Python environment, install the optional example dependencies: + +```bash +python -m pip install -e './examples/synthetic_xr[franka]' +``` + +Start the existing CloudXR service with its Lab WebXR profile and the newly built client. For automated setup, use OOB hub-only mode; no ADB is required. Do not start a competing runtime if Lab or the service already owns one. + +In that Lab environment, run the native recording script through the observation wrapper, using an output directory that does not already exist: + +```bash +SYNTHETIC_XR_VISIBLE=1 python -m isaaccapture_examples.synthetic_xr.franka.observe_record_demos \ + --lab-root /absolute/path/to/IsaacLab \ + --output /absolute/path/to/run/observations \ + --task IsaacContrib-Stack-Cube-Franka-IK-Abs \ + --dataset_file /absolute/path/to/run/franka.hdf5 \ + --mcap_record_path /absolute/path/to/run/controllers.mcap \ + --num_demos 1 --xr --viz kit --cloudxr_env none --no-auto_launch_cloudxr env.seed=42 +``` + +Open a dedicated desktop browser profile with loopback remote debugging enabled, using the actual client URL and `#/sim/isaaclab` application fragment: + +```bash +google-chrome --user-data-dir=/tmp/synthetic-xr-profile \ + --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 \ + 'https://HOST:PORT/client/?syntheticXR=1&immersiveMode=vr#/sim/isaaclab' +``` + +If GPU context creation fails while running Lab, the validated fallback uses `--use-gl=angle --use-angle=swiftshader --enable-unsafe-swiftshader --disable-gpu-compositing` and client query parameters `perEyeWidth=512&perEyeHeight=448`. This is a functional configuration, not a rendering-performance baseline. + +Connect the client. Keep exactly one tab with `syntheticXR=1` in this browser. Run the task logic in a second terminal: + +```bash +python -m isaaccapture_examples.synthetic_xr.franka.stack_logic \ + --cdp-url http://127.0.0.1:9222 \ + --feedback-url http://127.0.0.1:8765/state \ + --output /absolute/path/to/run/logic +``` + +The script uses the public `window.syntheticXR` API via CDP. CDP is needed only for this external Python driver; interactive desktop controls need no debugging port. Feedback is served on loopback and must be fresh. The script stops after one native successful export and checks settled cube geometry. Stage and native-action acknowledgement traces are retained in the output directories. + +The observer requires Lab's `create_environment`, `create_environment_config`, `setup_teleop_device` and `process_success_condition` integration hooks plus the task's cube/tool names. It fails if those hooks or shapes are incompatible; it does not silently substitute a task. Use `--stop-after approach` or `--stop-after grasp` to inspect intermediate stages without claiming a completed episode. + +## Scope + +Controller input only in this preview. Generated hand/body inputs and SO101 portability are separate follow-ups. No headset tracking, glove protocol or physical robot qualification is implied. Stop input releases synthetic buttons; use the application stop control for task shutdown. Simulation ground truth drives the stacking example, so its output is synthetic demonstration data. diff --git a/examples/synthetic_xr/VALIDATION.txt b/examples/synthetic_xr/VALIDATION.txt new file mode 100644 index 0000000000..c46eed554e --- /dev/null +++ b/examples/synthetic_xr/VALIDATION.txt @@ -0,0 +1,48 @@ +Synthetic XR Teleoperation 0.1.0 preview — validation, 2026-10-15 + +Qualified configuration: +- New WebXR client based on Isaac Capture e07a2cc8c3fcbc8d0aac9f9bdc7919a792983e44 plus this feature branch. +- Isaac Teleop 1.5+local native backend; CloudXR Runtime 6.3.0. +- Isaac Lab cb9628bee016c532feb5fc36c3b012fe707bed1b. +- IsaacContrib-Stack-Cube-Franka-IK-Abs, seed 42, exactly one episode. +- Desktop Chromium/IWER; real OOB/CDP CONNECT; no ADB or physical headset. +- SwiftShader and CPU compositing, requested 512x448 per eye. + +Observed run unattended-1792077046755219888: +PASS one native HDF5 episode, 1484 samples. +PASS native last-ten-frame success and independent settled stack geometry. +PASS 194 distinct browser command acknowledgements matched native actions; +maximum position difference 1.6069864620120102e-7 metres. +PASS clean native/browser shutdown; no forced SDK process cleanup. +Host video 80.25 seconds shows browser input on the left and Lab on the right. +The successful native export triggers a scene reset in the streamed browser; +that is not a second recorded episode. + +Native controller MCAP replay: +PASS exactly one offline replay session, no OpenXR session created. +72.63292288-second MCAP; 2158 records per controller, one absent record and +2157 present records. Every present aim/grip position, validity, button and +axis payload reproduced exactly and in order. Right controller: 196 distinct +states including trigger 0 and 1. Altered-position negative control rejected. +Quaternion equality is not covered by this viewer projection. +Native replay polls at 60 Hz and consumes records faster than this recording's +rate: present payloads end at approximately 38 seconds, followed by empty polls +until the duration timer expires. This verifies values/order, not pacing. +The 78.2-second viewer video preserves this behavior instead of trimming it. + +Software checks: +130 JavaScript unit tests, 4 browser tests, 7 Python example tests. +TypeScript checks, production build, Sphinx warnings-as-errors docs build, +and repository-wide pre-commit checks are required for the release archive. +The browser-only tests mock CloudXR transport; the native Franka run does not. + +Limits: +This is a developer preview for the exact compatibility configuration above. +It is not qualification of the current Isaac Capture native backend, physical +Pico/Quest tracking, hand/glove protocols, SO101, body tracking or real robots. +Four failed setup/integration attempts are retained before the successful fifth +attempt; these runs are not a reliability sample. No official SDK release or +main-branch merge is implied. + +Detailed evidence, source hashes, logs, videos and the final check receipts are +in the accompanying release evidence archive and SHA256 manifest. diff --git a/examples/synthetic_xr/pyproject.toml b/examples/synthetic_xr/pyproject.toml new file mode 100644 index 0000000000..65dd418271 --- /dev/null +++ b/examples/synthetic_xr/pyproject.toml @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "isaaccapture-examples-synthetic-xr" +version = "0.1.0" +description = "Synthetic XR Teleoperation desktop examples" +readme = "README.md" +license = "Apache-2.0" +license-files = ["LICENSE.md"] +requires-python = ">=3.10,<3.14" + +[project.optional-dependencies] +franka = ["numpy>=1.23", "scipy>=1.10", "websocket-client>=1.6"] + +[project.scripts] +synthetic-xr = "isaaccapture_examples.synthetic_xr.launch:main" + +[tool.hatch.build.targets.wheel] +only-include = ["python/isaaccapture_examples/synthetic_xr"] +sources = ["python"] diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/__init__.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/__init__.py new file mode 100644 index 0000000000..60403b8337 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Desktop synthetic XR examples; host runtime installation is separate.""" diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/__init__.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/__init__.py new file mode 100644 index 0000000000..59b9104404 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Feedback-driven Franka example through the browser controller API.""" diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/browser_command.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/browser_command.py new file mode 100644 index 0000000000..6b67b66e77 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/browser_command.py @@ -0,0 +1,147 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Use the public browser input API; read simulation observations over loopback.""" + +import atexit +import json +from pathlib import Path +import time +from urllib.parse import urlsplit +from urllib.request import urlopen +import uuid + +import websocket + +_cdp_url = "http://127.0.0.1:9222" +_feedback_url = "http://127.0.0.1:8765/state" +_socket = None +_sequence = 0 +_output = None + + +def configure(cdp_url, feedback_url, output=None): + global _cdp_url, _feedback_url, _output + for value in (cdp_url, feedback_url): + parsed = urlsplit(value) + if parsed.scheme != "http" or parsed.hostname not in { + "localhost", + "127.0.0.1", + "::1", + }: + raise ValueError( + "Example control and observation endpoints must use loopback HTTP" + ) + _cdp_url, _feedback_url = cdp_url.rstrip("/"), feedback_url + if output is not None: + _output = Path(output) / "commands" + _output.mkdir(parents=True, exist_ok=True) + + +def _read_json(url): + with urlopen(url, timeout=5) as response: + return json.load(response) + + +def close(): + global _socket + if _socket is not None: + _socket.close() + _socket = None + + +atexit.register(close) + + +def command(action, timeout=20, **parameters): + global _socket, _sequence + if _socket is None: + pages = [ + page + for page in _read_json(_cdp_url + "/json/list") + if page.get("type") == "page" and "syntheticXR=1" in page.get("url", "") + ] + if len(pages) != 1: + raise RuntimeError( + "Open exactly one Synthetic XR client tab in the debugging browser" + ) + _socket = websocket.create_connection( + pages[0]["webSocketDebuggerUrl"], timeout=timeout, suppress_origin=True + ) + payload = {"action": action, **parameters} + expression = ( + """(c => { + const api = window.syntheticXR; + if (!api) throw new Error('Synthetic XR API is not ready'); + if (c.action === 'pose') { + const update = {}; + if (c.position) update.position = c.position; + if (c.quaternion) update.quaternion = c.quaternion; + if (c.buttons && c.buttons.trigger !== undefined) update.trigger = c.buttons.trigger; + if (c.buttons && c.buttons.squeeze !== undefined) update.grip = c.buttons.squeeze; + api.setController(c.hand || 'right', update); + } else if (c.action === 'head') { + const update = {}; + if (c.position) update.position = c.position; + if (c.quaternion) update.quaternion = c.quaternion; + api.setHead(update); + } else if (c.action === 'start') { + const button = document.getElementById('synthetic-xr-start-teleop'); + if (!button || button.disabled) throw new Error('Connect the client before starting teleoperation'); + button.click(); + } else if (c.action === 'stop') { + api.stop(); + } else if (c.action !== 'inspect') { + throw new Error('Unsupported synthetic input command'); + } + if (c.action === 'inspect' && (!api.lastFrame || performance.now() - api.lastFrame.time > 1000)) + throw new Error('No fresh WebXR frame; connect the client first'); + return {state: api.getState(), frame: api.lastFrame}; + })(""" + + json.dumps(payload, allow_nan=False) + + ")" + ) + _sequence += 1 + _socket.send( + json.dumps( + { + "id": _sequence, + "method": "Runtime.evaluate", + "params": { + "expression": expression, + "returnByValue": True, + "awaitPromise": True, + }, + } + ) + ) + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + message = json.loads(_socket.recv()) + if message.get("id") != _sequence: + continue + result = message.get("result", {}) + if "error" in message or "exceptionDetails" in result: + raise RuntimeError( + "Browser rejected synthetic input: " + json.dumps(message) + ) + receipt = { + "id": uuid.uuid4().hex, + "timestamp": time.time(), + "ok": True, + "command": payload, + "browser": result["result"]["value"], + } + if _output is not None: + (_output / ("command-" + receipt["id"] + ".json")).write_text( + json.dumps(receipt, indent=2) + ) + return receipt + raise TimeoutError("Browser did not acknowledge synthetic input") + + +def state(max_age=5): + result = _read_json(_feedback_url) + if time.time() - result.get("timestamp", 0) > max_age: + raise RuntimeError("Simulator observation is stale") + return result diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py new file mode 100644 index 0000000000..799c9850a2 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py @@ -0,0 +1,300 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Observe native Franka recording without replacing its retargeting pipeline. + +Robot actions come exclusively through the native WebXR/CloudXR device. +Only read-only feedback, visual presentation and orderly shutdown hooks are added. +""" + +import argparse +import hashlib +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +import importlib.util +import inspect +import json +import os +from pathlib import Path +import sys +import threading +import time + +import numpy as np +from scipy.spatial.transform import Rotation + + +parser = argparse.ArgumentParser(add_help=False) +parser.add_argument("--lab-root", type=Path, required=True) +parser.add_argument("--output", type=Path, required=True) +parser.add_argument("--feedback-port", type=int, default=8765) +options, remaining = parser.parse_known_args() +ROOT = options.output.resolve() +REFERENCE = options.lab_root.resolve() / "scripts/tools/record_demos.py" +if not REFERENCE.is_file(): + raise FileNotFoundError(REFERENCE) +sys.argv = [str(REFERENCE), *remaining] +spec = importlib.util.spec_from_file_location("synthetic_xr_record_demos", REFERENCE) +native = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = native +spec.loader.exec_module(native) + +state = {"phase": "initializing", "steps": 0, "success_steps": 0} +snapshot = json.dumps(state).encode() +last_publish = 0.0 +environment = None +teleop = None +home = None +clutch_node = None +run_dir = ROOT +run_dir.mkdir(parents=True, exist_ok=False) +trace = (run_dir / "sim-feedback.jsonl").open("x", buffering=1) +state["run_dir"] = str(run_dir) + + +def values(data): + data = data.torch if hasattr(data, "torch") else data + return data.detach().cpu().tolist() + + +def publish(force=False): + global snapshot, last_publish + now = time.monotonic() + if not force and now - last_publish < 0.1: + return + if environment is None: + return + last_publish = now + env = environment + robot = env.scene["robot"] + ee = env.scene["ee_frame"].data + state.update( + { + "timestamp": time.time(), + "monotonic": now, + "simulation_dt": float(env.step_dt), + "joint_names": list(robot.joint_names), + "joints": values(robot.data.joint_pos), + "base_position_world": values(robot.data.root_pos_w), + "base_quaternion_world": values(robot.data.root_quat_w), + "ee_positions_base": values(ee.target_pos_source), + "ee_quaternions_base": values(ee.target_quat_source), + "ee_positions_world": values(ee.target_pos_w), + "ee_quaternions_world": values(ee.target_quat_w), + "body_names": list(robot.body_names), + "body_positions_world": values(robot.data.body_pos_w), + "body_quaternions_world": values(robot.data.body_quat_w), + "cubes": { + name: { + "position_world": values(env.scene[name].data.root_pos_w), + "quaternion_world": values(env.scene[name].data.root_quat_w), + "linear_velocity_world": values( + env.scene[name].data.root_lin_vel_w + ), + } + for name in ("cube_1", "cube_2", "cube_3") + }, + "exported_successful_episodes": env.recorder_manager.exported_successful_episode_count, + "configured_home_base_T_ee": home.tolist(), + "gripper_joint_names": env.cfg.gripper_joint_names, + "gripper_open_val": env.cfg.gripper_open_val, + } + ) + if teleop is not None: + state["world_T_anchor"] = teleop._anchor_manager.get_world_matrix().tolist() + target = ( + teleop._get_target_frame_T_world() + if teleop._cfg.target_frame_prim_path is not None + else None + ) + state["base_T_world"] = None if target is None else np.asarray(target).tolist() + ctrl = teleop.last_control_events + state["control"] = { + "is_active": ctrl.is_active, + "should_reset": ctrl.should_reset, + } + snapshot = json.dumps(state, allow_nan=False).encode() + trace.write(snapshot.decode() + "\n") + temporary = ROOT / "sim-state.tmp" + temporary.write_bytes(snapshot) + temporary.replace(ROOT / "sim-state.json") + + +class Feedback(BaseHTTPRequestHandler): + def do_GET(self): + if self.path != "/state": + self.send_error(404) + return + body = snapshot + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def log_message(self, *args): + pass + + +original_create = native.create_environment +original_config = native.create_environment_config + + +def create_environment_config(*args, **kwargs): + cfg, success, use_capture = original_config(*args, **kwargs) + if os.environ.get("SYNTHETIC_XR_VISIBLE") == "1": + from isaaclab_visualizers.kit import KitVisualizerCfg + + cfg.sim.visualizer_cfgs = [ + KitVisualizerCfg( + eye=(1.35, -1.35, 1.10), + lookat=(0.43, 0.0, 0.20), + focal_length=24.0, + enable_live_plots=False, + enable_markers=False, + ) + ] + cfg.scene.ee_frame.debug_vis = False + return cfg, success, use_capture + + +def create_environment(cfg): + global environment + env = original_create(cfg) + environment = env.unwrapped + if os.environ.get("SYNTHETIC_XR_VISIBLE") == "1": + import omni.ui + + # Keep the live viewport large enough to compare with the browser. + for window in omni.ui.Workspace.get_windows(): + if window.title in { + "Stage", + "Property", + "Content", + "Content Browser", + "Console", + "IsaacLab", + "Render Settings", + }: + window.visible = False + + def measure_home(): + global home + ee = environment.scene["ee_frame"].data + home = np.eye(4, dtype=np.float32) + home[:3, 3] = values(ee.target_pos_source)[0][0] + home[:3, :3] = Rotation.from_quat( + values(ee.target_quat_source)[0][0] + ).as_matrix() + + # The task randomizes joints at reset. Capture the actual reset pose each + # time, before the native recorder forwards its RESET event to the device. + original_reset = env.reset + + def reset(*args, **kwargs): + result = original_reset(*args, **kwargs) + measure_home() + return result + + env.reset = reset + measure_home() + state["phase"] = "scene_ready" + state["adapter"] = "None: original Franka pipeline_builder retained" + builder = cfg.isaac_teleop.pipeline_builder + state["native_pipeline_builder"] = builder.__module__ + "." + builder.__qualname__ + state["native_pipeline_builder_sha256"] = hashlib.sha256( + Path(inspect.getfile(builder)).read_bytes() + ).hexdigest() + state["native_record_script_sha256"] = hashlib.sha256( + REFERENCE.read_bytes() + ).hexdigest() + original_step = env.step + + def step(actions): + result = original_step(actions) + state["phase"] = "running" + state["steps"] += 1 + state["last_action"] = values(actions) + if os.environ.get("SYNTHETIC_XR_VISIBLE") == "1" and state["steps"] == 1: + from omni.kit.xr.core import XRSettings + import omni.ui + + XRSettings.get_singleton().set_setting( + "profile/persistent/mirror/displayMode", "viewport" + ) + for window in omni.ui.Workspace.get_windows(): + if window.title in { + "Layer", + "Layers", + "XR", + "Stage", + "Property", + "Content", + "Content Browser", + "Console", + "IsaacLab", + "Render Settings", + }: + window.visible = False + publish(force=True) + return result + + env.step = step + original_render = env.sim.render + + def render(*args, **kwargs): + result = original_render(*args, **kwargs) + publish() + return result + + env.sim.render = render + publish(force=True) + return env + + +original_setup = native.setup_teleop_device + + +def setup_device(*args, **kwargs): + global teleop + if os.environ.get("SYNTHETIC_XR_VISIBLE") == "1": + import carb.settings + + # The GUI normally requires the operator to enable the XR profile. + # Use the same native lifecycle auto-start path after bridge extensions load. + carb.settings.get_settings().set("/isaaclab/xr/auto_start", True) + teleop = original_setup(*args, **kwargs) + original_advance = teleop.advance + + def advance(*args, **kwargs): + if (run_dir / "stop").exists(): + raise KeyboardInterrupt("Synthetic XR example requested stop") + return original_advance(*args, **kwargs) + + teleop.advance = advance + state["phase"] = "waiting_for_xr" + return teleop + + +original_success = native.process_success_condition + + +def process_success(env, term, count): + result = original_success(env, term, count) + state["success_steps"] = result[0] + state["success_reset_needed"] = result[1] + publish() + return result + + +native.create_environment = create_environment +native.create_environment_config = create_environment_config +native.setup_teleop_device = setup_device +native.process_success_condition = process_success +server = ThreadingHTTPServer(("127.0.0.1", options.feedback_port), Feedback) +threading.Thread(target=server.serve_forever, daemon=True).start() +try: + native.main() +finally: + server.shutdown() + server.server_close() + trace.close() diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py new file mode 100644 index 0000000000..30c639587f --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py @@ -0,0 +1,150 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Scripted Franka stacking through browser XR input, with read-only scene feedback.""" + +import argparse +import json +import time +from pathlib import Path + +import numpy as np +from scipy.spatial.transform import Rotation + +from .browser_command import command, configure, state +from .xr_controller_bridge import XRControllerBridge, play, wait_steps + +parser = argparse.ArgumentParser() +parser.add_argument( + "--stop-after", choices=["approach", "grasp", "stack"], default="stack" +) +parser.add_argument("--cdp-url", default="http://127.0.0.1:9222") +parser.add_argument("--feedback-url", default="http://127.0.0.1:8765/state") +parser.add_argument("--output", type=Path, required=True) +args = parser.parse_args() +configure(args.cdp_url, args.feedback_url, args.output) +run = args.output / ("stack-logic-" + str(time.time_ns())) +run.mkdir(parents=True) +trace = (run / "stages.jsonl").open("x", buffering=1) +TOOL_OFFSET = np.array( + [0.0, 0.0, 0.1034] +) # Native Franka ee_frame offset from panda_hand. + + +def mark(stage): + s = state() + trace.write(json.dumps({"stage": stage, "state": s}) + "\n") + print( + stage, + json.dumps( + { + "steps": s["steps"], + "ee": s["ee_positions_world"][0][0], + "joints": s["joints"][0], + "cubes": {k: v["position_world"][0] for k, v in s["cubes"].items()}, + } + ), + flush=True, + ) + return s + + +def cube(name): + return np.asarray(state()["cubes"][name]["position_world"])[0] + + +def move_tool(controller, point, rotation, seconds=2): + point = np.asarray(point) + controller.move_world(point - rotation @ TOOL_OFFSET, rotation, seconds=seconds) + for _ in range(4): + if state().get("exported_successful_episodes", 0): + return + error = point - np.asarray(state()["ee_positions_world"])[0, 0] + if np.linalg.norm(error) < 0.002: + return + target = np.linalg.inv(controller.base_to_world) @ controller.target + controller.move_world( + target[:3, 3] + np.clip(error, -0.01, 0.01), rotation, seconds=0.6 + ) + if np.linalg.norm(point - np.asarray(state()["ee_positions_world"])[0, 0]) > 0.01: + raise RuntimeError("Gripper did not converge to the requested approach point") + + +def transfer(controller, name, support): + start = cube(name) + yaw = Rotation.from_quat(state()["cubes"][name]["quaternion_world"][0]).as_euler( + "xyz" + )[2] + orientation = ( + Rotation.from_euler("z", yaw) * Rotation.from_euler("x", np.pi) + ).as_matrix() + ee = np.asarray(state()["ee_positions_world"])[0, 0] + move_tool(controller, [ee[0], ee[1], max(0.23, ee[2])], orientation, seconds=2) + move_tool(controller, start + [0, 0, 0.14], orientation, seconds=3) + mark(name + "_above") + if args.stop_after == "approach": + return False + move_tool(controller, start + [0, 0, 0.003], orientation, seconds=3) + mark(name + "_at_grasp") + controller.target_base( + controller.target[:3, 3], controller.target[:3, :3], trigger=1.0 + ) + wait_steps(count=20) + mark(name + "_closed") + move_tool(controller, start + [0, 0, 0.16], orientation, seconds=3) + lifted = mark(name + "_lifted") + if cube(name)[2] - start[2] < 0.08: + raise RuntimeError(f"{name} did not lift") + if args.stop_after == "grasp": + return False + goal = cube(support) + [0, 0, 0.0468] + held_offset = cube(name) - np.asarray(lifted["ee_positions_world"])[0, 0] + move_tool(controller, goal - held_offset + [0, 0, 0.12], orientation, seconds=3) + move_tool(controller, goal - held_offset + [0, 0, 0.003], orientation, seconds=3) + for _ in range(3): + error = goal + [0, 0, 0.003] - cube(name) + if np.linalg.norm(error) < 0.003: + break + target = np.linalg.inv(controller.base_to_world) @ controller.target + controller.move_world( + target[:3, 3] + np.clip(error, -0.01, 0.01), orientation, seconds=0.6 + ) + mark(name + "_placed") + controller.target_base( + controller.target[:3, 3], controller.target[:3, :3], trigger=0.0 + ) + wait_steps(count=5) + ee = np.asarray(state()["ee_positions_world"])[0, 0] + move_tool(controller, ee + [0, 0, 0.13], orientation, seconds=3) + wait_steps(count=30) + mark(name + "_released") + delta = cube(name) - cube(support) + if np.linalg.norm(delta[:2]) > 0.015 or abs(delta[2] - 0.0468) > 0.006: + raise RuntimeError(f"{name} did not settle on {support}: {delta.tolist()}") + return True + + +controller = None +try: + command("head", quaternion=Rotation.from_euler("x", -0.55).as_quat().tolist()) + controller = XRControllerBridge() + if not state().get("control", {}).get("is_active"): + play() + controller.target_base(controller.target[:3, 3], controller.target[:3, :3]) + mark("started") + if transfer(controller, "cube_2", "cube_1"): + if transfer(controller, "cube_3", "cube_2"): + final = wait_steps(count=30) + if not final.get("exported_successful_episodes", 0): + raise RuntimeError( + "Stack sequence ended without native successful export" + ) + print("STACK_LOGIC_END", run, flush=True) +except Exception as error: + (run / "failure.txt").write_text(repr(error) + "\n") + print("STACK_LOGIC_FAILED", repr(error), flush=True) + raise +finally: + if controller is not None: + controller.release() + trace.close() diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py new file mode 100644 index 0000000000..19309048db --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py @@ -0,0 +1,168 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Convert Franka hand-body targets to IWER inputs for the native SE3 retargeter.""" + +import json +from pathlib import Path +import time + +import numpy as np +from scipy.spatial.transform import Rotation, Slerp + +from .browser_command import command, state + + +def transform(position, quaternion): + result = np.eye(4) + result[:3, 3] = position + result[:3, :3] = Rotation.from_quat(quaternion).as_matrix() + return result + + +def wait_steps(count=3, timeout=20): + first = state()["steps"] + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + current = state() + if current["steps"] >= first + count or current.get( + "exported_successful_episodes", 0 + ): + return current + time.sleep(0.05) + raise TimeoutError("Native simulation did not advance") + + +def play(): + # Start through the existing client handler, not a layout-dependent controller ray. + command("start") + return wait_steps() + + +class XRControllerBridge: + def __init__(self): + initial = state() + self.base_to_world = ( + np.eye(4) + if initial["base_T_world"] is None + else np.asarray(initial["base_T_world"]) + ) + self.base_to_anchor = self.base_to_world @ np.asarray(initial["world_T_anchor"]) + # The native Franka task config applies +90 degrees intrinsic X to grip orientation. + self.offset = Rotation.from_euler("X", 90, degrees=True).as_matrix() + command("pose", hand="right", buttons={"squeeze": 1, "trigger": 0}) + time.sleep(0.3) + frame = command("inspect")["browser"]["frame"] + right = next(s for s in frame["sources"] if s["hand"] == "right") + self.aim_to_grip = np.linalg.inv(transform(**right["aim"])) @ transform( + **right["grip"] + ) + index = initial["body_names"].index("panda_hand") + self.target = self.base_to_world @ transform( + initial["body_positions_world"][0][index], + initial["body_quaternions_world"][0][index], + ) + self.trigger = 0.0 + self.ack_trace = (Path(initial["run_dir"]) / "controller-acks.jsonl").open( + "a", buffering=1 + ) + # Place the virtual controller at the current robot target before starting native physics. + self.target_base(self.target[:3, 3], self.target[:3, :3], wait_receipt=False) + + def target_base(self, position, rotation, trigger=None, wait_receipt=True): + p, rot = np.asarray(position), np.asarray(rotation) + if ( + p.shape != (3,) + or rot.shape != (3, 3) + or not np.isfinite(p).all() + or not np.isfinite(rot).all() + ): + raise ValueError("Invalid target pose") + if np.linalg.norm(p) > 1.5: + raise ValueError("Target outside declared Franka test workspace") + self.trigger = self.trigger if trigger is None else float(trigger) + if not 0 <= self.trigger <= 1: + raise ValueError("Invalid trigger") + base_grip = np.eye(4) + base_grip[:3, 3] = p + base_grip[:3, :3] = rot @ self.offset.T + aim = ( + np.linalg.inv(self.base_to_anchor) + @ base_grip + @ np.linalg.inv(self.aim_to_grip) + ) + result = command( + "pose", + position=aim[:3, 3].tolist(), + quaternion=Rotation.from_matrix(aim[:3, :3]).as_quat().tolist(), + buttons={"squeeze": 1, "trigger": self.trigger}, + ) + self.target = transform(p, Rotation.from_matrix(rot).as_quat()) + if not wait_receipt: + return result + wanted_q = Rotation.from_matrix(rot).as_quat() + gripper = -1.0 if self.trigger > 0.5 else 1.0 + deadline = time.monotonic() + 8 + while time.monotonic() < deadline: + observed = state() + if observed.get("exported_successful_episodes", 0): + return result + action = np.asarray(observed["last_action"])[0] + if ( + observed["timestamp"] >= result["timestamp"] + and np.linalg.norm(action[:3] - p) < 1e-4 + and abs(float(action[3:7] @ wanted_q)) > 0.99999 + and abs(action[7] - gripper) < 0.001 + ): + self.ack_trace.write( + json.dumps( + { + "timestamp": time.time(), + "browser_command_id": result["id"], + "browser_ack_timestamp": result["timestamp"], + "native_step": observed["steps"], + "native_timestamp": observed["timestamp"], + "raw_trigger": self.trigger, + "expected_native_action": [ + *p.tolist(), + *wanted_q.tolist(), + gripper, + ], + "received_native_action": action.tolist(), + } + ) + + "\n" + ) + return result + time.sleep(0.025) + raise TimeoutError( + f"XR target did not reach native Franka action: expected={p.tolist()}, {wanted_q.tolist()}, {gripper}; actual={action.tolist()}" + ) + + def move_world(self, position, rotation=None, trigger=None, seconds=2): + current_world = np.linalg.inv(self.base_to_world) @ self.target + rotation = current_world[:3, :3] if rotation is None else rotation + target = self.base_to_world @ transform( + position, Rotation.from_matrix(rotation).as_quat() + ) + origin = self.target.copy() + steps = max(2, round(seconds * 5)) + orientations = Slerp( + [0, 1], Rotation.from_matrix([origin[:3, :3], target[:3, :3]]) + ) + for fraction in np.linspace(0, 1, steps + 1)[1:]: + if state().get("exported_successful_episodes", 0): + return state() + self.target_base( + origin[:3, 3] * (1 - fraction) + target[:3, 3] * fraction, + orientations(fraction).as_matrix(), + trigger, + ) + wait_steps(count=max(1, round(seconds / steps / state()["simulation_dt"]))) + return wait_steps() + + def release(self): + try: + command("pose", buttons={"squeeze": 0, "trigger": self.trigger}) + finally: + self.ack_trace.close() diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/launch.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/launch.py new file mode 100644 index 0000000000..01fa9ac269 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/launch.py @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Open the existing WebXR client with synthetic controls enabled.""" + +import argparse +from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit +import webbrowser + + +def client_url(value): + parts = urlsplit(value) + if ( + parts.scheme not in {"http", "https"} + or not parts.hostname + or parts.username + or parts.password + ): + raise ValueError("Use an HTTP(S) client URL without embedded credentials") + query = dict(parse_qsl(parts.query, keep_blank_values=True)) + query["syntheticXR"] = "1" + query.setdefault("immersiveMode", "vr") + return urlunsplit(parts._replace(query=urlencode(query))) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--client-url", + required=True, + help="URL printed by your CloudXR host; use the client built from this branch", + ) + parser.add_argument( + "--print-url", action="store_true", help="Print without opening a browser" + ) + args = parser.parse_args() + try: + url = client_url(args.client_url) + except ValueError as error: + parser.error(str(error)) + print(url) + if not args.print_url and not webbrowser.open(url): + parser.exit(1, "Could not open a browser; open the URL above manually.\n") + + +if __name__ == "__main__": + main() diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/replay_once.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/replay_once.py new file mode 100644 index 0000000000..6dd606a981 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/replay_once.py @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Replay native controller MCAP once using the SDK example's replay pipeline.""" + +import argparse +import importlib +from pathlib import Path +import sys +import time + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("mcap") + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=8090) + parser.add_argument("--hold-seconds", type=float, default=5) + parser.add_argument( + "--legacy-example-dir", + type=Path, + help="Explicit Isaac Teleop 1.5 examples/mcap_record_replay/python directory", + ) + args = parser.parse_args() + if not 0 <= args.hold_seconds <= 300: + parser.error("--hold-seconds must be between 0 and 300") + if args.legacy_example_dir is None: + from isaaccapture_examples.mcap_record_replay import replay_controller as replay + else: + directory = args.legacy_example_dir.resolve() + if not all( + (directory / name).is_file() + for name in ("replay_controller.py", "common.py") + ): + parser.error( + "Legacy directory must contain replay_controller.py and common.py" + ) + # The 1.5 example uses sibling imports; this explicit option selects that source. + sys.path.insert(0, str(directory)) + replay = importlib.import_module("replay_controller") + + path = replay.resolve_mcap(args.mcap) + duration = replay.mcap_duration_s(path) + if duration <= 0: + parser.error("MCAP has no positive-duration recording") + server = replay.viser.ViserServer(host=args.host, port=args.port) + try: + if args.legacy_example_dir is None: + replay.setup_scene(server) + else: + server.scene.set_up_direction("+y") + server.scene.add_grid( + name="/grid", width=2.0, height=2.0, plane="xz", cell_size=0.1 + ) + left = replay.ControllerViz(server, "controller_left", replay.LEFT_COLOR) + right = replay.ControllerViz(server, "controller_right", replay.RIGHT_COLOR) + print(f"Single replay viewer: http://{args.host}:{args.port}", flush=True) + frames = replay.run_once(path, duration, left, right) + if frames <= 0: + raise RuntimeError("Replay produced no frames") + print(f"Single replay complete: {frames} frames", flush=True) + time.sleep(args.hold_seconds) + finally: + server.stop() + + +if __name__ == "__main__": + main() diff --git a/examples/synthetic_xr/tests/test_examples.py b/examples/synthetic_xr/tests/test_examples.py new file mode 100644 index 0000000000..efe9998eb0 --- /dev/null +++ b/examples/synthetic_xr/tests/test_examples.py @@ -0,0 +1,94 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Check launcher routing and one-shot replay ownership without native libraries.""" + +from contextlib import redirect_stdout +import io +from pathlib import Path +import sys +from types import ModuleType, SimpleNamespace +import unittest +from unittest.mock import Mock, patch + +from isaaccapture_examples.synthetic_xr.launch import client_url +from isaaccapture_examples.synthetic_xr import replay_once + + +class LauncherTests(unittest.TestCase): + def test_preserves_service_route_and_application(self): + self.assertEqual( + client_url( + "https://host:48322/client/?serverIP=gpu&codec=h264#/sim/isaaclab" + ), + "https://host:48322/client/?serverIP=gpu&codec=h264&syntheticXR=1&immersiveMode=vr#/sim/isaaclab", + ) + + def test_replaces_disabled_flag_and_preserves_explicit_mode(self): + self.assertEqual( + client_url("https://host/?syntheticXR=0&immersiveMode=ar"), + "https://host/?syntheticXR=1&immersiveMode=ar", + ) + + def test_rejects_local_files_and_credentials(self): + for url in ( + "file:///tmp/index.html", + "javascript:alert(1)", + "https://user:secret@host/", + "https:///client", + ): + with self.subTest(url=url), self.assertRaises(ValueError): + client_url(url) + + +class ReplayTests(unittest.TestCase): + def invoke(self, frames=12, error=None, duration=1.0): + server = Mock() + replay = SimpleNamespace( + resolve_mcap=Mock(return_value=Path("input.mcap")), + mcap_duration_s=Mock(return_value=duration), + viser=SimpleNamespace(ViserServer=Mock(return_value=server)), + setup_scene=Mock(), + ControllerViz=Mock(), + LEFT_COLOR=1, + RIGHT_COLOR=2, + run_once=Mock(return_value=frames, side_effect=error), + ) + module = ModuleType("isaaccapture_examples.mcap_record_replay") + module.replay_controller = replay + patches = patch.dict(sys.modules, {module.__name__: module}) + with ( + patches, + patch.object( + sys, "argv", ["replay_once", "input.mcap", "--hold-seconds", "0"] + ), + redirect_stdout(io.StringIO()), + ): + try: + replay_once.main() + finally: + self.replay, self.server = replay, server + + def test_replays_exactly_once_and_closes_viewer(self): + self.invoke() + self.replay.run_once.assert_called_once() + self.server.stop.assert_called_once() + + def test_zero_frames_cannot_pass(self): + with self.assertRaisesRegex(RuntimeError, "no frames"): + self.invoke(frames=0) + self.server.stop.assert_called_once() + + def test_native_failure_propagates_and_closes_viewer(self): + with self.assertRaisesRegex(RuntimeError, "native failure"): + self.invoke(error=RuntimeError("native failure")) + self.server.stop.assert_called_once() + + def test_empty_recording_is_rejected_before_viewer_start(self): + with redirect_stdout(io.StringIO()), self.assertRaises(SystemExit): + self.invoke(duration=0) + self.replay.viser.ViserServer.assert_not_called() + + +if __name__ == "__main__": + unittest.main() From 63d760b1eca823873b6a08421dd3553d84cba5b7 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Thu, 15 Oct 2026 17:46:04 +0000 Subject: [PATCH 02/11] docs: lead synthetic XR preview with automated Franka workflow Signed-off-by: Sameer Tangade --- docs/AGENTS.md | 6 + docs/source/_static/css/custom.css | 10 + .../_static/synthetic-xr-shared-flow.svg | 3 + docs/source/teleop/synthetic_xr.rst | 232 +++++++----------- docs/source/teleop/synthetic_xr_reference.rst | 154 ++++++++++++ examples/synthetic_xr/README.md | 76 +++--- 6 files changed, 304 insertions(+), 177 deletions(-) create mode 100644 docs/source/_static/synthetic-xr-shared-flow.svg create mode 100644 docs/source/teleop/synthetic_xr_reference.rst diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 6e115c5416..fd08cb3f59 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -126,3 +126,9 @@ folder placement alone does not create sidebar nesting. The sidebar's top layer is a root toctree `:caption:` in `index.rst`; pages and their own toctrees nest below it. When a request names a sidebar layer, change that layer: do not add a landing page where a caption was asked for. + +## Synthetic XR preview narrative + +Lead with the automated robot task and show where human and generated input +join the same native path. Keep manual controller controls and recording tools +in the optional reference; do not present them as prerequisites for task automation. diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index ebaec1fd17..53c6adfeb9 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -273,3 +273,13 @@ figure a.no-image-zoom:has(> img) { .trademark-notice { color: var(--pst-color-text-muted) !important; } + +/* Leave room for both navigation toggles on narrow documentation screens. */ +@media (max-width: 575.98px) { + .bd-header .logo__title { + max-width: calc(100vw - 9rem); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} diff --git a/docs/source/_static/synthetic-xr-shared-flow.svg b/docs/source/_static/synthetic-xr-shared-flow.svg new file mode 100644 index 0000000000..d2412c7b26 --- /dev/null +++ b/docs/source/_static/synthetic-xr-shared-flow.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e59c3061dcdd88d127b7c615314ec070dcc9c0c1e04ac26f489c98e53af541dc +size 9193 diff --git a/docs/source/teleop/synthetic_xr.rst b/docs/source/teleop/synthetic_xr.rst index 54d05b5666..1a07713c5f 100644 --- a/docs/source/teleop/synthetic_xr.rst +++ b/docs/source/teleop/synthetic_xr.rst @@ -4,162 +4,112 @@ Synthetic XR Teleoperation ========================== -Control virtual XR inputs from your desktop browser, without a physical headset. -Use interactive controls or generated controller trajectories to develop Isaac -Teleop applications, record inputs and reproduce behavior. This preview uses the -existing IWER fallback in the WebXR client and the normal CloudXR transport. +**Build and automate teleoperation from your desktop, without an XR headset.** +Supply virtual controller inputs through IWER and use the same WebXR, CloudXR +and native robot-control path as a human operator. Start with Franka stacking, +then bring your own task logic. -Requirements ------------- - -Complete the host setup in :doc:`quick_start`. The same CloudXR runtime, GPU, -network and certificate requirements apply. Isaac Lab is needed only for the -Franka simulation example. Desktop operation requires neither ADB nor a headset. -Native headset sessions retain their own XR runtime; this feature does not -replace it with IWER. - -Build the client from this checkout following -:doc:`/development/build_from_source/webxr`. Start the service with that build: - -.. code-block:: bash - - export TELEOP_WEB_CLIENT_STATIC_DIR="$PWD/deps/cloudxr/webxr_client/build" - python -m isaaccapture.cloudxr.service start --host-client - -Use the client URL printed by the service. Open **Synthetic XR controls** to -reload with the panel enabled, or add ``syntheticXR=1`` to the URL query. -For an optional desktop launcher, install the example from this checkout: - -.. code-block:: bash - - python -m pip install -e ./examples/synthetic_xr - synthetic-xr --client-url 'https://HOST:PORT/client/' - -Replace the URL with the service's actual URL, keeping its query parameters and -application fragment. The launcher preserves them. It opens the client; it does -not start another runtime. For unattended hub setup without ADB, see -:doc:`/references/oob_teleop_control` and ``TELEOP_OOB_HUB_ONLY=1``. - -Move controllers ----------------- - -#. Connect the WebXR client and start a native input viewer or robot application. -#. Select the **Left** or **Right** controller in the Synthetic XR panel. -#. Change X/Y/Z in metres or rotate around an axis using the ten-degree buttons. -#. Use **Grip / clutch** and **Trigger** according to the application's mapping. -#. Use **Stop input** to cancel synthetic playback and release both buttons. - -**Reset virtual poses** resets input poses, not the simulator. **Stop input** -is an input stop, not an application emergency stop: use the robot application's -stop control to stop its task. Switching to a hidden browser tab also stops -synthetic playback and releases buttons. - -Controller poses use IWER local coordinates: X right, Y up, Z backward, with -quaternions in x/y/z/w order. They describe the controller's target-ray pose; -its grip pose additionally includes the selected IWER profile's grip offset. -The browser produces poses and buttons; host retargeting and a simulation task's -IK solver determine robot joint motion. - -Run and record a generated sequence ------------------------------------ - -**Run controller sweep** generates a nine-second sequence with separate left -and right movement, a grip interval and a trigger interval. It runs once and -releases buttons on completion. It requires no captured manual demonstration. - -Use **Record input** while moving the virtual controllers, then **Save input -trace**. Load that JSON with **Load trace**, then select **Replay once**. -This trace records complete synthetic input states at command times; playback -holds each state until the next timestamp. It is distinct from the client's -existing XR-frame recording JSON and from native MCAP. - -Only one input producer may own synthetic control. Manual/API pose changes are -rejected while a trace is playing. The client suspends synthetic controls while -its existing browser-recording replay owns the CloudXR tracking adapter. - -Native MCAP record and single replay ------------------------------------- - -Install the existing :doc:`/references/mcap_record_replay` example. With the -native runtime and desktop client connected, record a controller sequence: - -.. code-block:: bash +Franka simulation example +------------------------- - python -m isaaccapture_examples.mcap_record_replay.record_controller 15 controllers.mcap +The example locates a cube, approaches it, closes the gripper, lifts it and +places it on another cube. It repeats the transfer to build a three-cube stack +and stops after one successful native episode export. No recorded human +movement or manual controller positioning is required. -Run the sweep during that recording. Then replay the MCAP once using this -package's wrapper around the native example's replay pipeline: +The host-side ``stack_logic.py`` reads cube and gripper poses from Lab and +chooses the next gripper target. A bridge converts that target into synthetic +XR controller input. **Lab's native IK calculates the joint commands.** The +Franka you see in the browser is the streamed result of the simulation. -.. code-block:: bash +One shared robot-control path +----------------------------- - python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap +A human observes the streamed scene and moves a physical controller. Automation +reads numerical scene feedback and generates virtual controller poses. Both +feed the same downstream path in this Franka example: -For an Isaac Teleop 1.5 installation, add ``--legacy-example-dir`` pointing to -that SDK checkout's ``examples/mcap_record_replay/python`` directory. This -explicitly selects its native replay implementation. The default command -requires the current Isaac Capture example package. The native example polls at -60 Hz; a lower-rate recording may exhaust its payloads before its duration timer -expires and then show invalid inputs. Payload equality and order are verified; -original sample timing is not part of this preview's qualification. +.. figure:: /_static/synthetic-xr-shared-flow.svg + :alt: Human: streamed scene, visual decisions, physical controller movement and tracked poses. Automation: numerical Lab feedback, task logic, coordinate conversion and IWER poses. Both converge at WebXR and CloudXR, then native retargeting, Lab IK and simulated robot motion. Rendered images return to the browser; added numerical feedback returns to the script. + :width: 100% + :target: ../_static/synthetic-xr-shared-flow.svg -Open the printed viewer URL. The wrapper plays one sequence, holds the final -view for five seconds and closes. Native MCAP replay needs no live XR session. -The main MCAP example retains its existing repeated-playback behavior. + Different input producers; shared transport, retargeting, IK and simulation. + Open the diagram to view it at full size. -Script your own input ---------------------- +The numerical feedback is an extra, read-only observation path for the script. +In the human workflow, the operator sees the cubes and robot in streamed images; +this example's structured cube-pose feed is not part of that browser view. -In a desktop client opened with ``syntheticXR=1``, the preview API is available -as ``window.syntheticXR``. For example, in browser developer tools: +The script generates intermediate **3D positions and orientations**, rather +than joint angles or directional button presses. The bridge accounts for robot, +world and XR coordinates and the controller/gripper offsets. Native retargeting +maps the resulting controller poses back to robot targets; IK acts inside Lab. +Trigger values close or open the gripper, just as a physical trigger would. -.. code-block:: javascript +Try the example +--------------- - const input = window.syntheticXR; - input.setController('right', { - position: [0.3, 1.3, -0.4], - quaternion: [0, 0, 0, 1], - grip: 1, - trigger: 0.5, - }); - input.stop(); +Use a configured CloudXR and Isaac Lab host, this preview's built WebXR client +and the example's optional Franka dependencies. A headset and ADB are not needed; +CloudXR's GPU, network and certificate requirements still apply. -The reusable implementation is -:code-file:`src/syntheticXR/controller.ts `. -``setHead``, ``getState``, ``record``, ``finishRecording``, ``play`` and ``stop`` -provide input generation and reproduction. ``play(trace)`` runs once; -``play(trace, {loop: true})`` repeats until stopped. ``lastFrame`` exposes observed -WebXR aim/grip poses and gamepad values for debugging, not host acknowledgements. +Follow the :download:`Franka setup and run commands <../../../examples/synthetic_xr/README.md>`: -Inputs must be finite, positions within ten metres per axis, normalized -quaternions and button values in [0, 1]. Trace version 1 uses -``referenceSpace: 'iwer-local'`` and strictly increasing ``timeMs`` values -starting at zero, with complete ``head``, ``left`` and ``right`` state in each -frame. Traces are limited to ten minutes and 36,000 frames. Schedule button -transitions far enough apart to be observed by the application's frame loop. +#. Start the CloudXR service and Lab recording task with the read-only observer. +#. Connect the desktop WebXR client with ``syntheticXR=1`` enabled. +#. Run ``stack_logic``. It drives the synthetic controller through the browser + API, including task start, movement and gripping. -Franka simulation example -------------------------- +The Synthetic XR panel lets you see the input being applied. **You do not need +to select Left/Right or operate its movement buttons for this example.** -The :code-file:`Franka walkthrough ` runs the -native Lab recording script with read-only scene observations. Its -``stack_logic`` computes targets and submits them to the same browser API; -robot actuation still passes through WebXR, CloudXR, retargeting and native IK. -It uses the Lab task's ground truth rather than a learned policy or perception. -The observer depends on the documented Lab integration hooks listed in the -walkthrough; verify compatibility before using another Lab revision. +The preview's native result is one successful seed-42 episode on the prepared +TeleOp 1.5 / CloudXR 6.3 / Lab compatibility stack. Targets use current cube +poses, but repeated randomized episodes have not been qualified. See the +:download:`validation scope <../../../examples/synthetic_xr/VALIDATION.txt>` for component +identities and evidence limits. -Scope and troubleshooting +Bring your own task logic ------------------------- -* **Controls unavailable:** check that desktop IWER loaded and the URL has - ``syntheticXR=1``. Native immersive XR sessions intentionally keep their input. -* **No movement:** verify connection, fresh native poses, clutch and application - start state. Moving a controller is not itself a robot task success signal. -* **Replay rejected:** synthetic traces, browser recordings and MCAP are separate - formats. Use the corresponding replay path. -* **No WebGL:** use a browser/driver combination that supports WebGL2. Do not - treat an unavailable renderer as a successful transport test. - -This controller preview does not emulate physical tracking accuracy, haptics, -Pico/Quest device behavior or glove protocols. Hand/body adapters are outside -this release. Scripted simulation data is synthetic, not a human demonstration. +Keep the virtual controller and native pipeline; change the decisions that +drive them. For example, a pick-and-place script could choose a destination +region instead of another cube as the placement target. + +.. code-block:: text + + Your task logic + observe object and gripper poses + choose approach -> grasp -> lift -> destination -> release + generate gripper targets and open/close commands + | + v + Robot-specific pose bridge -> browser API / IWER + | + v + Shared WebXR -> CloudXR -> retargeting -> IK -> robot simulation + +Use these pieces as the starting point: + +* :download:`stack_logic.py <../../../examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py>` + chooses targets and checks grasp, placement and task completion. Replace these + task decisions with your own; this is scripted logic, not a learned model. +* :download:`xr_controller_bridge.py <../../../examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py>` + interpolates movement, converts Franka targets into XR controller poses and + checks that the corresponding action reaches the native task. Reuse its + mapping for the same configured Franka task; another robot needs its own + retargeting and coordinate mapping. +* ``window.syntheticXR.setController(...)`` supplies controller poses and + buttons. The :doc:`browser API reference ` describes + coordinates and optional inspection/recording tools. + +The feedback source and task checks belong to your application. New tasks need +their own validation; the stacking example is not a general motion planner. +This preview covers controller input, not physical headset tracking, glove +protocols or hand/body input. + +.. toctree:: + :hidden: + + synthetic_xr_reference diff --git a/docs/source/teleop/synthetic_xr_reference.rst b/docs/source/teleop/synthetic_xr_reference.rst new file mode 100644 index 0000000000..e84b3368c0 --- /dev/null +++ b/docs/source/teleop/synthetic_xr_reference.rst @@ -0,0 +1,154 @@ +.. SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +.. SPDX-License-Identifier: Apache-2.0 + +Synthetic XR controls and recording reference +============================================= + +These optional controls help inspect input and reproduce issues. The automated +Franka example drives the controller API itself; selecting a controller or +moving it with the panel is not a prerequisite. Start with +:doc:`synthetic_xr` for the task workflow and shared native path. + +Requirements +------------ + +Complete the host setup in :doc:`quick_start`. The same CloudXR runtime, GPU, +network and certificate requirements apply. Isaac Lab is needed only for the +Franka simulation example. Desktop operation requires neither ADB nor a headset. +Native headset sessions retain their own XR runtime; this feature does not +replace it with IWER. + +Build the client from this checkout following +:doc:`/development/build_from_source/webxr`. Start the service with that build: + +.. code-block:: bash + + export TELEOP_WEB_CLIENT_STATIC_DIR="$PWD/deps/cloudxr/webxr_client/build" + python -m isaaccapture.cloudxr.service start --host-client + +Use the client URL printed by the service. Open **Synthetic XR controls** to +reload with the panel enabled, or add ``syntheticXR=1`` to the URL query. +For an optional desktop launcher, install the example from this checkout: + +.. code-block:: bash + + python -m pip install -e ./examples/synthetic_xr + synthetic-xr --client-url 'https://HOST:PORT/client/' + +Replace the URL with the service's actual URL, keeping its query parameters and +application fragment. The launcher preserves them. It opens the client; it does +not start another runtime. For unattended hub setup without ADB, see +:doc:`/references/oob_teleop_control` and ``TELEOP_OOB_HUB_ONLY=1``. + +Move controllers +---------------- + +#. Connect the WebXR client and start a native input viewer or robot application. +#. Select the **Left** or **Right** controller in the Synthetic XR panel. +#. Change X/Y/Z in metres or rotate around an axis using the ten-degree buttons. +#. Use **Grip / clutch** and **Trigger** according to the application's mapping. +#. Use **Stop input** to cancel synthetic playback and release both buttons. + +**Reset virtual poses** resets input poses, not the simulator. **Stop input** +is an input stop, not an application emergency stop: use the robot application's +stop control to stop its task. Switching to a hidden browser tab also stops +synthetic playback and releases buttons. + +Controller poses use IWER local coordinates: X right, Y up, Z backward, with +quaternions in x/y/z/w order. They describe the controller's target-ray pose; +its grip pose additionally includes the selected IWER profile's grip offset. +The browser produces poses and buttons; host retargeting and a simulation task's +IK solver determine robot joint motion. + +Run and record a generated sequence +----------------------------------- + +**Run controller sweep** generates a nine-second sequence with separate left +and right movement, a grip interval and a trigger interval. It runs once and +releases buttons on completion. It requires no captured manual demonstration. + +Use **Record input** while moving the virtual controllers, then **Save input +trace**. Load that JSON with **Load trace**, then select **Replay once**. +This trace records complete synthetic input states at command times; playback +holds each state until the next timestamp. It is distinct from the client's +existing XR-frame recording JSON and from native MCAP. + +Only one input producer may own synthetic control. Manual/API pose changes are +rejected while a trace is playing. The client suspends synthetic controls while +its existing browser-recording replay owns the CloudXR tracking adapter. + +Native MCAP record and single replay +------------------------------------ + +Install the existing :doc:`/references/mcap_record_replay` example. With the +native runtime and desktop client connected, record a controller sequence: + +.. code-block:: bash + + python -m isaaccapture_examples.mcap_record_replay.record_controller 15 controllers.mcap + +Run the sweep during that recording. Then replay the MCAP once using this +package's wrapper around the native example's replay pipeline: + +.. code-block:: bash + + python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap + +For an Isaac Teleop 1.5 installation, add ``--legacy-example-dir`` pointing to +that SDK checkout's ``examples/mcap_record_replay/python`` directory. This +explicitly selects its native replay implementation. The default command +requires the current Isaac Capture example package. The native example polls at +60 Hz; a lower-rate recording may exhaust its payloads before its duration timer +expires and then show invalid inputs. Payload equality and order are verified; +original sample timing is not part of this preview's qualification. + +Open the printed viewer URL. The wrapper plays one sequence, leaves the viewer open for five additional +seconds and closes. This does not guarantee a held final valid pose. Native MCAP replay needs no live XR session. +The main MCAP example retains its existing repeated-playback behavior. + +Browser input API +----------------- + +In a desktop client opened with ``syntheticXR=1``, the preview API is available +as ``window.syntheticXR``. For example, in browser developer tools: + +.. code-block:: javascript + + const input = window.syntheticXR; + input.setController('right', { + position: [0.3, 1.3, -0.4], + quaternion: [0, 0, 0, 1], + grip: 1, + trigger: 0.5, + }); + input.stop(); + +The reusable implementation is +:download:`src/syntheticXR/controller.ts <../../../deps/cloudxr/webxr_client/src/syntheticXR/controller.ts>`. +``setHead``, ``getState``, ``record``, ``finishRecording``, ``play`` and ``stop`` +provide input generation and reproduction. ``play(trace)`` runs once; +``play(trace, {loop: true})`` repeats until stopped. ``lastFrame`` exposes observed +WebXR aim/grip poses and gamepad values for debugging, not host acknowledgements. + +Inputs must be finite, positions within ten metres per axis, normalized +quaternions and button values in [0, 1]. Trace version 1 uses +``referenceSpace: 'iwer-local'`` and strictly increasing ``timeMs`` values +starting at zero, with complete ``head``, ``left`` and ``right`` state in each +frame. Traces are limited to ten minutes and 36,000 frames. Schedule button +transitions far enough apart to be observed by the application's frame loop. + +Scope and troubleshooting +------------------------- + +* **Controls unavailable:** check that desktop IWER loaded and the URL has + ``syntheticXR=1``. Native immersive XR sessions intentionally keep their input. +* **No movement:** verify connection, fresh native poses, clutch and application + start state. Moving a controller is not itself a robot task success signal. +* **Replay rejected:** synthetic traces, browser recordings and MCAP are separate + formats. Use the corresponding replay path. +* **No WebGL:** use a browser/driver combination that supports WebGL2. Do not + treat an unavailable renderer as a successful transport test. + +This controller preview does not emulate physical tracking accuracy, haptics, +Pico/Quest device behavior or glove protocols. Hand/body adapters are outside +this release. Scripted simulation data is synthetic, not a human demonstration. diff --git a/examples/synthetic_xr/README.md b/examples/synthetic_xr/README.md index 5ce010e1d1..347a5c8ebb 100644 --- a/examples/synthetic_xr/README.md +++ b/examples/synthetic_xr/README.md @@ -5,42 +5,9 @@ SPDX-License-Identifier: Apache-2.0 # Synthetic XR Teleoperation — Preview -Use virtual controllers from the existing WebXR client. No physical headset or manual demonstration is required. CloudXR host requirements still apply. +Automate Franka cube stacking through virtual XR controllers and the native WebXR/CloudXR/retargeting/IK path. No physical headset or manual demonstration is required. CloudXR host requirements still apply. -## Desktop controls - -Build this checkout's WebXR client using `docs/source/development/build_from_source/webxr.rst`, then serve its `build/` directory through the existing CloudXR service's `--host-client` option and `TELEOP_WEB_CLIENT_STATIC_DIR`. Do not use the public prebuilt client for this preview; it does not contain this branch's controls. - -From the repository root: - -```bash -python -m pip install -e ./examples/synthetic_xr -synthetic-xr --client-url 'https://HOST:PORT/client/' -``` - -Replace the URL with the service's printed URL, retaining its query and application fragment. Connect, open the synthetic input panel and select a controller. For applications that require a start command, use **Start teleoperation**, which invokes the existing client handler. Change position or orientation, engage Grip / clutch, or change Trigger. Run controller sweep generates a nine-second sequence and releases both buttons when complete. - -Record input and Save input trace export a synthetic command trace. Load trace and Replay once reproduce it through IWER. The existing browser recorder and native MCAP use different formats and injection boundaries. See `docs/source/teleop/synthetic_xr.rst` for the API and trace contract. - -## Native controller MCAP - -Install this checkout's existing `examples/mcap_record_replay` package first. Start its controller recording with a connected desktop client and run the sweep during capture: - -```bash -python -m isaaccapture_examples.mcap_record_replay.record_controller 15 controllers.mcap -python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap -``` - -The second command opens a native replay viewer on loopback port 8090, plays once, leaves the viewer open for five seconds, then closes. No live XR session is required during replay. - -For the supported Isaac Teleop 1.5 compatibility stack, select the installed SDK's original example explicitly: - -```bash -python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap \ - --legacy-example-dir /absolute/path/to/IsaacCapture/examples/mcap_record_replay/python -``` - -This invokes that version's native replay pipeline; it does not emulate MCAP decoding or alias the newer SDK to the older one. Its replay loop polls at 60 Hz: a lower-rate recording can finish yielding payloads before the duration timer expires, leaving an invalid/empty view until exit. This preview verifies payload order and values, not reproduction of original sample timing. +Start with the [illustrated workflow and bring-your-own-task guide](../../docs/source/teleop/synthetic_xr.rst). This README contains the setup and run commands. ## Franka stacking @@ -54,7 +21,7 @@ In the configured Lab Python environment, install the optional example dependenc python -m pip install -e './examples/synthetic_xr[franka]' ``` -Start the existing CloudXR service with its Lab WebXR profile and the newly built client. For automated setup, use OOB hub-only mode; no ADB is required. Do not start a competing runtime if Lab or the service already owns one. +Build this checkout's WebXR client using `docs/source/development/build_from_source/webxr.rst`. Serve its `build/` directory using `TELEOP_WEB_CLIENT_STATIC_DIR` and the service's `--host-client` option; the public prebuilt client does not include this preview. Start the existing CloudXR service with its Lab WebXR profile and the newly built client. For automated setup, use OOB hub-only mode; no ADB is required. Do not start a competing runtime if Lab or the service already owns one. In that Lab environment, run the native recording script through the observation wrapper, using an output directory that does not already exist: @@ -87,10 +54,47 @@ python -m isaaccapture_examples.synthetic_xr.franka.stack_logic \ --output /absolute/path/to/run/logic ``` +The script starts teleoperation and supplies controller movement and grip/trigger input automatically. You do not need to select a controller or move it using the panel. + The script uses the public `window.syntheticXR` API via CDP. CDP is needed only for this external Python driver; interactive desktop controls need no debugging port. Feedback is served on loopback and must be fresh. The script stops after one native successful export and checks settled cube geometry. Stage and native-action acknowledgement traces are retained in the output directories. The observer requires Lab's `create_environment`, `create_environment_config`, `setup_teleop_device` and `process_success_condition` integration hooks plus the task's cube/tool names. It fails if those hooks or shapes are incompatible; it does not silently substitute a task. Use `--stop-after approach` or `--stop-after grasp` to inspect intermediate stages without claiming a completed episode. +## Optional: inspect desktop controller input + +Build this checkout's WebXR client using `docs/source/development/build_from_source/webxr.rst`, then serve its `build/` directory through the existing CloudXR service's `--host-client` option and `TELEOP_WEB_CLIENT_STATIC_DIR`. Do not use the public prebuilt client for this preview; it does not contain this branch's controls. + +From the repository root: + +```bash +python -m pip install -e ./examples/synthetic_xr +synthetic-xr --client-url 'https://HOST:PORT/client/' +``` + +Replace the URL with the service's printed URL, retaining its query and application fragment. Connect, open the synthetic input panel and select a controller. For applications that require a start command, use **Start teleoperation**, which invokes the existing client handler. Change position or orientation, engage Grip / clutch, or change Trigger. Run controller sweep generates a nine-second sequence and releases both buttons when complete. + +Record input and Save input trace export a synthetic command trace. Load trace and Replay once reproduce it through IWER. The existing browser recorder and native MCAP use different formats and injection boundaries. See `docs/source/teleop/synthetic_xr.rst` for the API and trace contract. + +## Optional: native controller MCAP + +Install this checkout's existing `examples/mcap_record_replay` package first. Start its controller recording with a connected desktop client and run the sweep during capture: + +```bash +python -m isaaccapture_examples.mcap_record_replay.record_controller 15 controllers.mcap +python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap +``` + +The second command opens a native replay viewer on loopback port 8090, plays once, leaves the viewer open for five seconds, then closes. No live XR session is required during replay. + +For the supported Isaac Teleop 1.5 compatibility stack, select the installed SDK's original example explicitly: + +```bash +python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap \ + --legacy-example-dir /absolute/path/to/IsaacCapture/examples/mcap_record_replay/python +``` + +This invokes that version's native replay pipeline; it does not emulate MCAP decoding or alias the newer SDK to the older one. Its replay loop polls at 60 Hz: a lower-rate recording can finish yielding payloads before the duration timer expires, leaving an invalid/empty view until exit. This preview verifies payload order and values, not reproduction of original sample timing. + ## Scope Controller input only in this preview. Generated hand/body inputs and SO101 portability are separate follow-ups. No headset tracking, glove protocol or physical robot qualification is implied. Stop input releases synthetic buttons; use the application stop control for task shutdown. Simulation ground truth drives the stacking example, so its output is synthetic demonstration data. From beb9b5257639395cbdcf7b5c83e51892964611a7 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Thu, 15 Oct 2026 18:03:56 +0000 Subject: [PATCH 03/11] docs: keep phase-one synthetic XR guide on one page Signed-off-by: Sameer Tangade --- docs/AGENTS.md | 4 +- docs/source/teleop/synthetic_xr.rst | 8 +- docs/source/teleop/synthetic_xr_reference.rst | 154 ------------------ 3 files changed, 3 insertions(+), 163 deletions(-) delete mode 100644 docs/source/teleop/synthetic_xr_reference.rst diff --git a/docs/AGENTS.md b/docs/AGENTS.md index fd08cb3f59..dd067393ff 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -130,5 +130,5 @@ that layer: do not add a landing page where a caption was asked for. ## Synthetic XR preview narrative Lead with the automated robot task and show where human and generated input -join the same native path. Keep manual controller controls and recording tools -in the optional reference; do not present them as prerequisites for task automation. +join the same native path. Keep the phase-one guide to one main page; defer +manual controller and recording walkthroughs rather than adding reference pages. diff --git a/docs/source/teleop/synthetic_xr.rst b/docs/source/teleop/synthetic_xr.rst index 1a07713c5f..dda23cdeed 100644 --- a/docs/source/teleop/synthetic_xr.rst +++ b/docs/source/teleop/synthetic_xr.rst @@ -101,15 +101,9 @@ Use these pieces as the starting point: mapping for the same configured Franka task; another robot needs its own retargeting and coordinate mapping. * ``window.syntheticXR.setController(...)`` supplies controller poses and - buttons. The :doc:`browser API reference ` describes - coordinates and optional inspection/recording tools. + buttons through the virtual controller. The feedback source and task checks belong to your application. New tasks need their own validation; the stacking example is not a general motion planner. This preview covers controller input, not physical headset tracking, glove protocols or hand/body input. - -.. toctree:: - :hidden: - - synthetic_xr_reference diff --git a/docs/source/teleop/synthetic_xr_reference.rst b/docs/source/teleop/synthetic_xr_reference.rst deleted file mode 100644 index e84b3368c0..0000000000 --- a/docs/source/teleop/synthetic_xr_reference.rst +++ /dev/null @@ -1,154 +0,0 @@ -.. SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -.. SPDX-License-Identifier: Apache-2.0 - -Synthetic XR controls and recording reference -============================================= - -These optional controls help inspect input and reproduce issues. The automated -Franka example drives the controller API itself; selecting a controller or -moving it with the panel is not a prerequisite. Start with -:doc:`synthetic_xr` for the task workflow and shared native path. - -Requirements ------------- - -Complete the host setup in :doc:`quick_start`. The same CloudXR runtime, GPU, -network and certificate requirements apply. Isaac Lab is needed only for the -Franka simulation example. Desktop operation requires neither ADB nor a headset. -Native headset sessions retain their own XR runtime; this feature does not -replace it with IWER. - -Build the client from this checkout following -:doc:`/development/build_from_source/webxr`. Start the service with that build: - -.. code-block:: bash - - export TELEOP_WEB_CLIENT_STATIC_DIR="$PWD/deps/cloudxr/webxr_client/build" - python -m isaaccapture.cloudxr.service start --host-client - -Use the client URL printed by the service. Open **Synthetic XR controls** to -reload with the panel enabled, or add ``syntheticXR=1`` to the URL query. -For an optional desktop launcher, install the example from this checkout: - -.. code-block:: bash - - python -m pip install -e ./examples/synthetic_xr - synthetic-xr --client-url 'https://HOST:PORT/client/' - -Replace the URL with the service's actual URL, keeping its query parameters and -application fragment. The launcher preserves them. It opens the client; it does -not start another runtime. For unattended hub setup without ADB, see -:doc:`/references/oob_teleop_control` and ``TELEOP_OOB_HUB_ONLY=1``. - -Move controllers ----------------- - -#. Connect the WebXR client and start a native input viewer or robot application. -#. Select the **Left** or **Right** controller in the Synthetic XR panel. -#. Change X/Y/Z in metres or rotate around an axis using the ten-degree buttons. -#. Use **Grip / clutch** and **Trigger** according to the application's mapping. -#. Use **Stop input** to cancel synthetic playback and release both buttons. - -**Reset virtual poses** resets input poses, not the simulator. **Stop input** -is an input stop, not an application emergency stop: use the robot application's -stop control to stop its task. Switching to a hidden browser tab also stops -synthetic playback and releases buttons. - -Controller poses use IWER local coordinates: X right, Y up, Z backward, with -quaternions in x/y/z/w order. They describe the controller's target-ray pose; -its grip pose additionally includes the selected IWER profile's grip offset. -The browser produces poses and buttons; host retargeting and a simulation task's -IK solver determine robot joint motion. - -Run and record a generated sequence ------------------------------------ - -**Run controller sweep** generates a nine-second sequence with separate left -and right movement, a grip interval and a trigger interval. It runs once and -releases buttons on completion. It requires no captured manual demonstration. - -Use **Record input** while moving the virtual controllers, then **Save input -trace**. Load that JSON with **Load trace**, then select **Replay once**. -This trace records complete synthetic input states at command times; playback -holds each state until the next timestamp. It is distinct from the client's -existing XR-frame recording JSON and from native MCAP. - -Only one input producer may own synthetic control. Manual/API pose changes are -rejected while a trace is playing. The client suspends synthetic controls while -its existing browser-recording replay owns the CloudXR tracking adapter. - -Native MCAP record and single replay ------------------------------------- - -Install the existing :doc:`/references/mcap_record_replay` example. With the -native runtime and desktop client connected, record a controller sequence: - -.. code-block:: bash - - python -m isaaccapture_examples.mcap_record_replay.record_controller 15 controllers.mcap - -Run the sweep during that recording. Then replay the MCAP once using this -package's wrapper around the native example's replay pipeline: - -.. code-block:: bash - - python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap - -For an Isaac Teleop 1.5 installation, add ``--legacy-example-dir`` pointing to -that SDK checkout's ``examples/mcap_record_replay/python`` directory. This -explicitly selects its native replay implementation. The default command -requires the current Isaac Capture example package. The native example polls at -60 Hz; a lower-rate recording may exhaust its payloads before its duration timer -expires and then show invalid inputs. Payload equality and order are verified; -original sample timing is not part of this preview's qualification. - -Open the printed viewer URL. The wrapper plays one sequence, leaves the viewer open for five additional -seconds and closes. This does not guarantee a held final valid pose. Native MCAP replay needs no live XR session. -The main MCAP example retains its existing repeated-playback behavior. - -Browser input API ------------------ - -In a desktop client opened with ``syntheticXR=1``, the preview API is available -as ``window.syntheticXR``. For example, in browser developer tools: - -.. code-block:: javascript - - const input = window.syntheticXR; - input.setController('right', { - position: [0.3, 1.3, -0.4], - quaternion: [0, 0, 0, 1], - grip: 1, - trigger: 0.5, - }); - input.stop(); - -The reusable implementation is -:download:`src/syntheticXR/controller.ts <../../../deps/cloudxr/webxr_client/src/syntheticXR/controller.ts>`. -``setHead``, ``getState``, ``record``, ``finishRecording``, ``play`` and ``stop`` -provide input generation and reproduction. ``play(trace)`` runs once; -``play(trace, {loop: true})`` repeats until stopped. ``lastFrame`` exposes observed -WebXR aim/grip poses and gamepad values for debugging, not host acknowledgements. - -Inputs must be finite, positions within ten metres per axis, normalized -quaternions and button values in [0, 1]. Trace version 1 uses -``referenceSpace: 'iwer-local'`` and strictly increasing ``timeMs`` values -starting at zero, with complete ``head``, ``left`` and ``right`` state in each -frame. Traces are limited to ten minutes and 36,000 frames. Schedule button -transitions far enough apart to be observed by the application's frame loop. - -Scope and troubleshooting -------------------------- - -* **Controls unavailable:** check that desktop IWER loaded and the URL has - ``syntheticXR=1``. Native immersive XR sessions intentionally keep their input. -* **No movement:** verify connection, fresh native poses, clutch and application - start state. Moving a controller is not itself a robot task success signal. -* **Replay rejected:** synthetic traces, browser recordings and MCAP are separate - formats. Use the corresponding replay path. -* **No WebGL:** use a browser/driver combination that supports WebGL2. Do not - treat an unavailable renderer as a successful transport test. - -This controller preview does not emulate physical tracking accuracy, haptics, -Pico/Quest device behavior or glove protocols. Hand/body adapters are outside -this release. Scripted simulation data is synthetic, not a human demonstration. From 1b0ab02b95b320785bb53e8d5f9d4e39ff0075b0 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Fri, 16 Oct 2026 07:01:15 +0000 Subject: [PATCH 04/11] docs: show multi-episode Franka teleoperation Signed-off-by: Sameer Tangade --- docs/source/_ext/ecosystem_grid.py | 23 +++++++++++++++++++ docs/source/_static/css/custom.css | 9 ++++++++ .../franka-synthetic-xr-multi-episode-2x.mp4 | 3 +++ docs/source/teleop/synthetic_xr.rst | 18 +++++++++++---- 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 docs/source/_static/franka-synthetic-xr-multi-episode-2x.mp4 diff --git a/docs/source/_ext/ecosystem_grid.py b/docs/source/_ext/ecosystem_grid.py index b3370a1533..8c80a5d353 100644 --- a/docs/source/_ext/ecosystem_grid.py +++ b/docs/source/_ext/ecosystem_grid.py @@ -699,6 +699,28 @@ def run(self) -> list[nodes.Node]: return [node] +class DocVideo(Directive): + """Embed a responsive video while retaining a link in non-HTML output.""" + + required_arguments = 1 + has_content = False + + def run(self) -> list[nodes.Node]: + source = self.arguments[0] + video = _element( + "video", + ["doc-video"], + controls="controls", + preload="metadata", + playsinline="playsinline", + src=source, + ) + fallback = nodes.paragraph() + fallback += nodes.reference("Open the video.", "Open the video.", refuri=source) + video += fallback + return [video] + + class DeviceCount(SphinxRole): """``:device-count:`input``` — rows in one table section, group, or ``all``. @@ -735,5 +757,6 @@ def setup(app): ) app.add_directive("device-matrix", DeviceMatrix) app.add_directive("eco-block", EcoBlock) + app.add_directive("doc-video", DocVideo) app.add_role("device-count", DeviceCount()) return {"version": "1.0", "parallel_read_safe": True, "parallel_write_safe": True} diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index 53c6adfeb9..f727738681 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -74,6 +74,15 @@ a:focus { text-decoration: underline !important; } +.doc-video { + display: block; + width: 100%; + margin: 1rem 0 0.5rem; + border: 1px solid var(--pst-color-border); + border-radius: 0.5rem; + background: #000; +} + /* for the announcement link */ .bd-header-announcement a, .bd-header-version-warning a { diff --git a/docs/source/_static/franka-synthetic-xr-multi-episode-2x.mp4 b/docs/source/_static/franka-synthetic-xr-multi-episode-2x.mp4 new file mode 100644 index 0000000000..f1316c69a0 --- /dev/null +++ b/docs/source/_static/franka-synthetic-xr-multi-episode-2x.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1ddd69cd94b09c5ac3390970c3e0fa5511d55d304b7218a8fab0f3308f28e33 +size 13130473 diff --git a/docs/source/teleop/synthetic_xr.rst b/docs/source/teleop/synthetic_xr.rst index dda23cdeed..0f895b41e8 100644 --- a/docs/source/teleop/synthetic_xr.rst +++ b/docs/source/teleop/synthetic_xr.rst @@ -14,14 +14,21 @@ Franka simulation example The example locates a cube, approaches it, closes the gripper, lifts it and places it on another cube. It repeats the transfer to build a three-cube stack -and stops after one successful native episode export. No recorded human -movement or manual controller positioning is required. +and the default command stops after one successful native episode export. No +recorded human movement or manual controller positioning is required. The host-side ``stack_logic.py`` reads cube and gripper poses from Lab and chooses the next gripper target. A bridge converts that target into synthetic XR controller input. **Lab's native IK calculates the joint commands.** The Franka you see in the browser is the streamed result of the simulation. +.. doc-video:: ../_static/franka-synthetic-xr-multi-episode-2x.mp4 + +**Three episodes at 2× speed.** The browser and Synthetic XR input are on the +left; Isaac Lab is on the right. After each native export, Lab randomizes the +cube layout and the same action logic starts the next episode. The original +full-speed recording and native artifacts are retained with the run evidence. + One shared robot-control path ----------------------------- @@ -64,9 +71,10 @@ Follow the :download:`Franka setup and run commands <../../../examples/synthetic The Synthetic XR panel lets you see the input being applied. **You do not need to select Left/Right or operate its movement buttons for this example.** -The preview's native result is one successful seed-42 episode on the prepared -TeleOp 1.5 / CloudXR 6.3 / Lab compatibility stack. Targets use current cube -poses, but repeated randomized episodes have not been qualified. See the +The preview's packaged native result is one successful seed-42 episode on the +prepared TeleOp 1.5 / CloudXR 6.3 / Lab compatibility stack. The accelerated +recording above additionally demonstrates three successful randomized episodes +in one continuous session; three successes are not a reliability claim. See the :download:`validation scope <../../../examples/synthetic_xr/VALIDATION.txt>` for component identities and evidence limits. From 9de97b020c90149eb639518e73e4db7d8a9c5698 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Fri, 16 Oct 2026 08:53:56 +0000 Subject: [PATCH 05/11] docs: trim SO-101 lead-in from Franka video Signed-off-by: Sameer Tangade --- docs/source/_static/franka-synthetic-xr-multi-episode-2x.mp4 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/_static/franka-synthetic-xr-multi-episode-2x.mp4 b/docs/source/_static/franka-synthetic-xr-multi-episode-2x.mp4 index f1316c69a0..1d2b1b793f 100644 --- a/docs/source/_static/franka-synthetic-xr-multi-episode-2x.mp4 +++ b/docs/source/_static/franka-synthetic-xr-multi-episode-2x.mp4 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1ddd69cd94b09c5ac3390970c3e0fa5511d55d304b7218a8fab0f3308f28e33 -size 13130473 +oid sha256:0aef45050c5bbb796802ddebdc68d794c5488d30df050f87b005aca0a712181a +size 12526503 From 0c010a815f11e09188409e625e3ad0bf5b89dd48 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Sun, 18 Oct 2026 10:24:30 +0000 Subject: [PATCH 06/11] feat: add supervised Synthetic XR task sessions and native success hooks Signed-off-by: Sameer Tangade --- docs/source/teleop/synthetic_xr.rst | 73 ++-- examples/synthetic_xr/README.md | 126 ++++--- examples/synthetic_xr/VALIDATION.txt | 93 ++--- examples/synthetic_xr/pyproject.toml | 2 +- .../synthetic_xr/franka/browser_command.py | 276 ++++++++------- .../franka/observe_record_demos.py | 122 ++++++- .../synthetic_xr/franka/observer_process.py | 113 ++++++ .../synthetic_xr/franka/session.py | 324 ++++++++++++++++++ .../synthetic_xr/franka/stack_logic.py | 203 +++++------ .../synthetic_xr/franka/task_hooks.py | 49 +++ .../franka/xr_controller_bridge.py | 56 +-- .../synthetic_xr/tests/test_task_session.py | 284 +++++++++++++++ 12 files changed, 1332 insertions(+), 389 deletions(-) create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observer_process.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_hooks.py create mode 100644 examples/synthetic_xr/tests/test_task_session.py diff --git a/docs/source/teleop/synthetic_xr.rst b/docs/source/teleop/synthetic_xr.rst index 0f895b41e8..f1b5d8906a 100644 --- a/docs/source/teleop/synthetic_xr.rst +++ b/docs/source/teleop/synthetic_xr.rst @@ -24,7 +24,7 @@ Franka you see in the browser is the streamed result of the simulation. .. doc-video:: ../_static/franka-synthetic-xr-multi-episode-2x.mp4 -**Three episodes at 2× speed.** The browser and Synthetic XR input are on the +**Earlier preview: three episodes at 2× speed.** The browser and Synthetic XR input are on the left; Isaac Lab is on the right. After each native export, Lab randomizes the cube layout and the same action logic starts the next episode. The original full-speed recording and native artifacts are retained with the run evidence. @@ -71,12 +71,14 @@ Follow the :download:`Franka setup and run commands <../../../examples/synthetic The Synthetic XR panel lets you see the input being applied. **You do not need to select Left/Right or operate its movement buttons for this example.** -The preview's packaged native result is one successful seed-42 episode on the -prepared TeleOp 1.5 / CloudXR 6.3 / Lab compatibility stack. The accelerated -recording above additionally demonstrates three successful randomized episodes -in one continuous session; three successes are not a reliability claim. See the -:download:`validation scope <../../../examples/synthetic_xr/VALIDATION.txt>` for component -identities and evidence limits. +The current candidate validates the session interface with one successful +seed-42 stacking episode on the TeleOp 1.5 / CloudXR 6.3 / Lab compatibility +stack. A separate customer-application acceptance check also verifies a custom +success callback and native dataset export. The earlier preview recording above +shows three randomized stacking episodes; it is not a reliability measurement +of the new interface. See the +:download:`validation scope <../../../examples/synthetic_xr/VALIDATION.txt>` for +component identities and evidence limits. Bring your own task logic ------------------------- @@ -98,20 +100,43 @@ region instead of another cube as the placement target. v Shared WebXR -> CloudXR -> retargeting -> IK -> robot simulation -Use these pieces as the starting point: - -* :download:`stack_logic.py <../../../examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py>` - chooses targets and checks grasp, placement and task completion. Replace these - task decisions with your own; this is scripted logic, not a learned model. -* :download:`xr_controller_bridge.py <../../../examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py>` - interpolates movement, converts Franka targets into XR controller poses and - checks that the corresponding action reaches the native task. Reuse its - mapping for the same configured Franka task; another robot needs its own - retargeting and coordinate mapping. -* ``window.syntheticXR.setController(...)`` supplies controller poses and - buttons through the virtual controller. - -The feedback source and task checks belong to your application. New tasks need -their own validation; the stacking example is not a general motion planner. -This preview covers controller input, not physical headset tracking, glove -protocols or hand/body input. +Start with the shipped :download:`stacking implementation <../../../examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py>`. +It uses ``FrankaTaskSession`` to observe the scene, move the tool and operate the +gripper. Replace its task decisions while keeping the session interface: + +.. code-block:: python + + from isaaccapture_examples.synthetic_xr.franka.session import FrankaTaskSession + + with FrankaTaskSession( + observation_dir="run/observations", output="run/my-task" + ) as session: + observation = session.observe() + # Your logic chooses a world-space tool target and unit xyzw quaternion. + session.move_tool(target_position, target_quaternion, seconds=2) + session.set_gripper(closed=True) + # Continue your approach, transfer and release sequence. + session.wait_for_export() + +The snippet illustrates the interface; the linked stacking implementation is +the complete runnable example. Positions use metres in the simulator's Z-up +world frame. ``move_tool`` targets the gripper tool frame; the Franka adapter +handles its offset and conversion into XR controller poses. The browser still +sends controller input through the shared native pipeline. + +**Define success as well as motion.** An application success callback receives +a detached observation after each simulation step and returns ``True`` only +when the task's actual completion criteria hold. Start the observer with +``--success my_task:is_success`` to select your callback. Lab applies its +consecutive-success requirement and performs the native episode export. +``wait_for_export`` confirms export and orderly observer completion; it never +forces a successful result. Without a callback, the original stacking success +criterion remains in effect. + +The :download:`setup and interface contract <../../../examples/synthetic_xr/README.md>` +contains the exact launch sequence, observation fields, callback contract and +shutdown instructions. The session retains command receipts, native-action +acknowledgements and named observations so you can inspect an unexpected result. +Another robot needs its own supported pose adapter and task configuration. +This interface supplies controller input; task planning, collision avoidance +and application-specific correctness remain the application's responsibility. diff --git a/examples/synthetic_xr/README.md b/examples/synthetic_xr/README.md index 347a5c8ebb..20f98150d9 100644 --- a/examples/synthetic_xr/README.md +++ b/examples/synthetic_xr/README.md @@ -3,98 +3,116 @@ SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All SPDX-License-Identifier: Apache-2.0 --> -# Synthetic XR Teleoperation — Preview +# Synthetic XR Teleoperation -Automate Franka cube stacking through virtual XR controllers and the native WebXR/CloudXR/retargeting/IK path. No physical headset or manual demonstration is required. CloudXR host requirements still apply. +Run Franka stacking through virtual XR controllers and the native WebXR → CloudXR → retargeting → Lab IK path. Start with the [illustrated tutorial](../../docs/source/teleop/synthetic_xr.rst). The browser shows streamed simulation; task logic does not animate the robot locally. -Start with the [illustrated workflow and bring-your-own-task guide](../../docs/source/teleop/synthetic_xr.rst). This README contains the setup and run commands. +## Supported setup -## Franka stacking +This integration targets **one local Linux simulation environment** with Lab `cb9628bee016c532feb5fc36c3b012fe707bed1b`, task `IsaacContrib-Stack-Cube-Franka-IK-Abs`, installed Isaac Teleop `1.5+local`, CloudXR `6.3.0`, and this checkout's built WebXR client. These identities describe a compatibility stack, not qualification of other Isaac Capture releases. See [VALIDATION.txt](VALIDATION.txt) for executed results and remaining limits. -This integration adds read-only observation hooks around Lab's `record_demos.py`. It does not replace the native task pipeline or write joint actions. `stack_logic` reads cube and tool poses, converts targets into XR controller poses and grip/trigger input, and waits for matching native actions. +Use an existing configured Lab Python environment, an installed CloudXR runtime, a graphical desktop, and Chrome/Chromium with WebXR support. Follow the product's CloudXR GPU, network, certificate and license setup first. All commands below run on the same host. The Python driver and observer share a local filesystem. Keep CDP and observations on loopback; no headset or ADB is required. -The integration is built for the prepared Lab revision `cb9628bee016c532feb5fc36c3b012fe707bed1b`, its `IsaacContrib-Stack-Cube-Franka-IK-Abs` task, CloudXR 6.3.0 and the installed Isaac Teleop 1.5 backend. The new client is based on Isaac Capture `e07a2cc8c3fcbc8d0aac9f9bdc7919a792983e44`. These component identities are distinct; a new-client check is not a fresh validation of every current SDK backend. See `VALIDATION.txt` for the measured result and limits before treating another combination as supported. - -In the configured Lab Python environment, install the optional example dependencies: +Set these paths in each terminal, activate your configured Lab environment, and work from the Isaac Capture checkout: ```bash +export CAPTURE_ROOT=/absolute/path/to/IsaacCapture +export LAB_ROOT=/absolute/path/to/IsaacLab +export CLOUDXR_INSTALL_DIR=/absolute/path/to/cloudxr +export SYNTHETIC_RUN=/absolute/path/to/a/new/run +cd "$CAPTURE_ROOT" python -m pip install -e './examples/synthetic_xr[franka]' ``` -Build this checkout's WebXR client using `docs/source/development/build_from_source/webxr.rst`. Serve its `build/` directory using `TELEOP_WEB_CLIENT_STATIC_DIR` and the service's `--host-client` option; the public prebuilt client does not include this preview. Start the existing CloudXR service with its Lab WebXR profile and the newly built client. For automated setup, use OOB hub-only mode; no ADB is required. Do not start a competing runtime if Lab or the service already owns one. +Build this checkout's client using [the WebXR build instructions](../../docs/source/development/build_from_source/webxr.rst). The public prebuilt client does not contain this feature. Keep the generated `deps/cloudxr/webxr_client/build/` directory. + +## 1. Start the service -In that Lab environment, run the native recording script through the observation wrapper, using an output directory that does not already exist: +Check that another service does not own the configured CloudXR ports. Run in a dedicated terminal: ```bash -SYNTHETIC_XR_VISIBLE=1 python -m isaaccapture_examples.synthetic_xr.franka.observe_record_demos \ - --lab-root /absolute/path/to/IsaacLab \ - --output /absolute/path/to/run/observations \ - --task IsaacContrib-Stack-Cube-Franka-IK-Abs \ - --dataset_file /absolute/path/to/run/franka.hdf5 \ - --mcap_record_path /absolute/path/to/run/controllers.mcap \ - --num_demos 1 --xr --viz kit --cloudxr_env none --no-auto_launch_cloudxr env.seed=42 +export TELEOP_WEB_CLIENT_STATIC_DIR="$CAPTURE_ROOT/deps/cloudxr/webxr_client/build" +export TELEOP_OOB_HUB_ONLY=1 +python -m isaacteleop.cloudxr.service run \ + --cloudxr-install-dir "$CLOUDXR_INSTALL_DIR" \ + --cloudxr-env-config "$LAB_ROOT/source/isaaclab_teleop/isaaclab_teleop/cloudxrjs-cloudxr.env" \ + --setup-oob --host-client ``` -Open a dedicated desktop browser profile with loopback remote debugging enabled, using the actual client URL and `#/sim/isaaclab` application fragment: +Accept the CloudXR license when prompted. For unattended use after accepting its terms, the service supports `--accept-eula`. Hub-only mode starts OOB without USB/ADB. Leave this terminal running; use its printed host/port if different from the defaults below. + +## 2. Start Lab and the observer + +In a second terminal, load the environment generated by the running service. Do not start a competing CloudXR runtime: ```bash -google-chrome --user-data-dir=/tmp/synthetic-xr-profile \ - --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 \ - 'https://HOST:PORT/client/?syntheticXR=1&immersiveMode=vr#/sim/isaaclab' +set -a +source "$CLOUDXR_INSTALL_DIR/run/cloudxr.env" +set +a +SYNTHETIC_XR_VISIBLE=1 python -m isaaccapture_examples.synthetic_xr.franka.observe_record_demos \ + --lab-root "$LAB_ROOT" \ + --output "$SYNTHETIC_RUN/observations" \ + --task IsaacContrib-Stack-Cube-Franka-IK-Abs \ + --dataset_file "$SYNTHETIC_RUN/franka.hdf5" \ + --num_demos 1 --xr --viz kit --cloudxr_env none --no-auto_launch_cloudxr env.seed=42 ``` -If GPU context creation fails while running Lab, the validated fallback uses `--use-gl=angle --use-angle=swiftshader --enable-unsafe-swiftshader --disable-gpu-compositing` and client query parameters `perEyeWidth=512&perEyeHeight=448`. This is a functional configuration, not a rendering-performance baseline. +The observation directory must not exist. Accept Kit's license prompt if this environment has not done so. After accepting its terms, unattended runs can set `OMNI_KIT_ACCEPT_EULA=YES`. Wait for Lab's XR/scene initialization and the loopback endpoint `http://127.0.0.1:8765/state`. No robot motion starts until the task driver starts teleoperation. -Connect the client. Keep exactly one tab with `syntheticXR=1` in this browser. Run the task logic in a second terminal: +## 3. Connect the browser + +Use a fresh dedicated profile and the local service certificate configured in the prerequisite setup: ```bash -python -m isaaccapture_examples.synthetic_xr.franka.stack_logic \ - --cdp-url http://127.0.0.1:9222 \ - --feedback-url http://127.0.0.1:8765/state \ - --output /absolute/path/to/run/logic +google-chrome --user-data-dir="$SYNTHETIC_RUN/browser-profile" \ + --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 \ + 'https://localhost:48322/client/?syntheticXR=1&immersiveMode=vr&oobEnable=1&serverIP=localhost&port=48322&codec=h264#/sim/isaaclab' ``` -The script starts teleoperation and supplies controller movement and grip/trigger input automatically. You do not need to select a controller or move it using the panel. +Click **CONNECT** and wait for the Synthetic XR panel to say **Connected**. Keep exactly one `syntheticXR=1` tab. The script starts teleoperation and moves the controllers; you do not need to select or manually move a controller. -The script uses the public `window.syntheticXR` API via CDP. CDP is needed only for this external Python driver; interactive desktop controls need no debugging port. Feedback is served on loopback and must be fresh. The script stops after one native successful export and checks settled cube geometry. Stage and native-action acknowledgement traces are retained in the output directories. +If Lab and the browser cannot create GPU contexts together, add `--use-gl=angle --use-angle=swiftshader --enable-unsafe-swiftshader --disable-gpu-compositing` and append `&perEyeWidth=512&perEyeHeight=448` before the URL's `#` fragment. This is a functional fallback, not a performance baseline. Resolve browser sandbox or certificate errors using your host/browser installation guidance; disabling those protections is not part of the supported setup recipe. -The observer requires Lab's `create_environment`, `create_environment_config`, `setup_teleop_device` and `process_success_condition` integration hooks plus the task's cube/tool names. It fails if those hooks or shapes are incompatible; it does not silently substitute a task. Use `--stop-after approach` or `--stop-after grasp` to inspect intermediate stages without claiming a completed episode. +## 4. Run stacking -## Optional: inspect desktop controller input - -Build this checkout's WebXR client using `docs/source/development/build_from_source/webxr.rst`, then serve its `build/` directory through the existing CloudXR service's `--host-client` option and `TELEOP_WEB_CLIENT_STATIC_DIR`. Do not use the public prebuilt client for this preview; it does not contain this branch's controls. - -From the repository root: +In a third terminal with the same paths and Python environment: ```bash -python -m pip install -e ./examples/synthetic_xr -synthetic-xr --client-url 'https://HOST:PORT/client/' +python -m isaaccapture_examples.synthetic_xr.franka.stack_logic \ + --observation-dir "$SYNTHETIC_RUN/observations" \ + --output "$SYNTHETIC_RUN/logic" ``` -Replace the URL with the service's printed URL, retaining its query and application fragment. Connect, open the synthetic input panel and select a controller. For applications that require a start command, use **Start teleoperation**, which invokes the existing client handler. Change position or orientation, engage Grip / clutch, or change Trigger. Run controller sweep generates a nine-second sequence and releases both buttons when complete. +A completed run contains a native successful HDF5 episode, `observations/exported-episode-1.json`, and a task `result.json` with `status: PASS`. The driver waits for native export, native environment closure and a zero worker exit status recorded by the observer supervisor. `stages.jsonl`, command receipts and `controller-acks.jsonl` expose the requested and received actions. `--stop-after approach` or `--stop-after grasp` is a partial diagnostic run and does not claim task completion. -Record input and Save input trace export a synthetic command trace. Load trace and Replay once reproduce it through IWER. The existing browser recorder and native MCAP use different formats and injection boundaries. See `docs/source/teleop/synthetic_xr.rst` for the API and trace contract. +## Application interface -## Optional: native controller MCAP +Import `FrankaTaskSession` from `isaaccapture_examples.synthetic_xr.franka.session`. Its context attaches to the configured observer, starts teleoperation, and releases synthetic inputs on exit. By default it requests observer shutdown on exit; it does not own or terminate your browser or CloudXR service. -Install this checkout's existing `examples/mcap_record_replay` package first. Start its controller recording with a connected desktop client and run the sweep during capture: +| Operation | Contract | +| --- | --- | +| `observe()` | Fresh schema-v1 dictionary; rejects stale feedback, a restarted observer, or a mismatched observation directory. | +| `move_tool(position, quaternion, seconds=2)` | Franka `ee_frame` target in world metres/Z-up; quaternion `[x,y,z,w]`, unit length. Interpolates XR input and checks native acknowledgement and tool convergence. | +| `set_gripper(closed=True)` | Boolean grip decision, converted to XR trigger; this does not write robot joints. | +| `wait(seconds)` | Wait in simulation seconds with a bounded wall-clock timeout. | +| `mark(name)` | Save a named observation to the task trace. | +| `wait_for_export(timeout=30)` | Wait for the native successful episode and orderly observer completion. Does not set a success flag. Use `--num_demos 1`. | -```bash -python -m isaaccapture_examples.mcap_record_replay.record_controller 15 controllers.mcap -python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap -``` +Observation fields: `run_id` identifies the observer, `episode_id` changes on reset, `steps` counts physics steps, `simulation_dt` is seconds/step, and `timestamp` is host Unix time. `ee_positions_world[0][0]` and `ee_quaternions_world[0][0]` describe the tool. `cubes[name]` contains `position_world[0]`, `quaternion_world[0]`, and `linear_velocity_world[0]`. Positions are metres; velocities are metres/second; all quaternions are xyzw. `joint_names` maps `joints[0]`. Select additional scene objects with the observer's `--objects name ...`; the `cubes` field name is retained for schema-v1 compatibility. -The second command opens a native replay viewer on loopback port 8090, plays once, leaves the viewer open for five seconds, then closes. No live XR session is required during replay. +The adapter owns the `panda_hand` → tool offset (0.1034 m along local Z) and XR/frame transforms. It remains specific to this Franka configuration. Targets are checked for finite values and the declared workspace; this is not a collision-avoidance or reachability planner. Native episode completion ends movement immediately, including any remaining interpolation; inspect the returned observation before issuing another action. Only one task driver may attach to an observer. Use a new observation directory for every run; a failed attachment does not take over an existing driver. -For the supported Isaac Teleop 1.5 compatibility stack, select the installed SDK's original example explicitly: +For your own task, use the shipped [stack_logic.py](python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py) as the runnable starting point and replace its decisions. Define a trusted Python callback `is_success(observation) -> bool` and start the observer with `--success my_task:is_success`; make `my_task` importable in the Lab environment. The callback receives a detached snapshot after each step. Use `episode_id` to reset any history. Exceptions and non-boolean returns abort the run. The callback is application code, not a sandbox. -```bash -python -m isaaccapture_examples.synthetic_xr.replay_once controllers.mcap \ - --legacy-example-dir /absolute/path/to/IsaacCapture/examples/mcap_record_replay/python -``` +The native recorder still requires its configured consecutive success steps (`--num_success_steps`, default 10), records state/actions, marks success and exports. An application callback changes the success criterion, not the motion pipeline. Include any required grasp/lift history, settling and release checks. A driver finishing its commands does not establish success. Without `--success`, the original native stacking criterion remains active. + +## Stop and troubleshoot -This invokes that version's native replay pipeline; it does not emulate MCAP decoding or alias the newer SDK to the older one. Its replay loop polls at 60 Hz: a lower-rate recording can finish yielding payloads before the duration timer expires, leaving an invalid/empty view until exit. This preview verifies payload order and values, not reproduction of original sample timing. +The session context releases controller input and requests observer shutdown on exceptions. The observer supervisor bounds the complete run to 900 seconds by default (`--session-timeout`); timeout or forced shutdown remains a failure. For an externally interrupted driver, create `$SYNTHETIC_RUN/observations/stop` to stop the observer, or interrupt its terminal. Close the dedicated browser and interrupt the service terminal when finished. Once a session attaches, the observer stops if it receives no driver heartbeat for 30 seconds (`--driver-timeout`). Session operations refresh this heartbeat. Long application computations must call `observe()` periodically or configure an appropriate timeout. This watchdog stops the simulation; it is not a real-robot safety system. A crashed driver cannot perform normal browser cleanup. -## Scope +- **Stale observation / no XR frame:** check Lab's terminal, CONNECT state and the matching client build; do not reuse a previous run's files. +- **Target not acknowledged:** inspect command/native receipts and connection state. The driver fails instead of silently moving the robot directly. +- **No native export:** inspect the success callback or native criterion. A log saying the script ended is insufficient. +- **Observer hook/configuration mismatch:** use the supported Lab revision. The adapter depends on native recording hooks and Franka scene names. -Controller input only in this preview. Generated hand/body inputs and SO101 portability are separate follow-ups. No headset tracking, glove protocol or physical robot qualification is implied. Stop input releases synthetic buttons; use the application stop control for task shutdown. Simulation ground truth drives the stacking example, so its output is synthetic demonstration data. +This release covers simulated controller input. Physical headset sensing, other robots, hand/body/glove input and real-robot safety are separate scopes. diff --git a/examples/synthetic_xr/VALIDATION.txt b/examples/synthetic_xr/VALIDATION.txt index c46eed554e..110a3b5724 100644 --- a/examples/synthetic_xr/VALIDATION.txt +++ b/examples/synthetic_xr/VALIDATION.txt @@ -1,48 +1,61 @@ -Synthetic XR Teleoperation 0.1.0 preview — validation, 2026-10-15 +Synthetic XR Teleoperation 0.2.0 release candidate — validation, 2026-10-18 -Qualified configuration: -- New WebXR client based on Isaac Capture e07a2cc8c3fcbc8d0aac9f9bdc7919a792983e44 plus this feature branch. +Supported configuration exercised: +- This feature branch's desktop WebXR client, IWER and OOB/CDP CONNECT. - Isaac Teleop 1.5+local native backend; CloudXR Runtime 6.3.0. - Isaac Lab cb9628bee016c532feb5fc36c3b012fe707bed1b. -- IsaacContrib-Stack-Cube-Franka-IK-Abs, seed 42, exactly one episode. -- Desktop Chromium/IWER; real OOB/CDP CONNECT; no ADB or physical headset. -- SwiftShader and CPU compositing, requested 512x448 per eye. +- IsaacContrib-Stack-Cube-Franka-IK-Abs, seed 42, one environment. +- Local Linux host, SwiftShader/CPU compositing, 512x448 per eye. +- No ADB or physical headset. Native retargeting and IK remain in Lab. -Observed run unattended-1792077046755219888: -PASS one native HDF5 episode, 1484 samples. -PASS native last-ten-frame success and independent settled stack geometry. -PASS 194 distinct browser command acknowledgements matched native actions; -maximum position difference 1.6069864620120102e-7 metres. -PASS clean native/browser shutdown; no forced SDK process cleanup. -Host video 80.25 seconds shows browser input on the left and Lab on the right. -The successful native export triggers a scene reset in the streamed browser; -that is not a second recorded episode. +Fresh stacking through the public FrankaTaskSession API: +Run 7c28fb4a6b2d47d1901f332f127718d4: PASS. +One native HDF5 episode, 1428 samples; 197 correlated browser/native commands. +Maximum position difference 1.6269140171458334e-7 metres. +Every recorded action matches the native feedback; final ten native states +satisfy independent three-cube stack geometry. Native environment closed, +worker exit 0, no forced shutdown. Full host/browser video 152.25 seconds. -Native controller MCAP replay: -PASS exactly one offline replay session, no OpenXR session created. -72.63292288-second MCAP; 2158 records per controller, one absent record and -2157 present records. Every present aim/grip position, validity, button and -axis payload reproduced exactly and in order. Right controller: 196 distinct -states including trigger 0 and 1. Altered-position negative control rejected. -Quaternion equality is not covered by this viewer projection. -Native replay polls at 60 Hz and consumes records faster than this recording's -rate: present payloads end at approximately 38 seconds, followed by empty polls -until the duration timer expires. This verifies values/order, not pacing. -The 78.2-second viewer video preserves this behavior instead of trimming it. +Customer task extension acceptance (separate from the stacking tutorial): +Run 79501bd58db34857969922c459d605d9: PASS. +A separate pick-and-place application uses only the documented session API and +success callback. It exports one native HDF5 episode with 761 samples and 108 +correlated commands; maximum position difference 1.6074436996170352e-7 metres. +Lift 0.15978 metres, final XY error below 0.000895 metres; last twenty native +states confirm placement, settling, open fingers and tool retraction. Worker +exit 0, no forced shutdown. Full host/browser video 223.35 seconds. +Lab records policy observations before a step and rigid-body states after it; +the verifier aligns these and uses raw finger joints (the processed second +finger observation has a negative sign). -Software checks: -130 JavaScript unit tests, 4 browser tests, 7 Python example tests. -TypeScript checks, production build, Sphinx warnings-as-errors docs build, -and repository-wide pre-commit checks are required for the release archive. -The browser-only tests mock CloudXR transport; the native Franka run does not. +Native driver-crash control: +Run e767207f7f6d4097aa2f4fdd685cefa8: expected failure detected. +The driver exits abruptly with status 23 after attachment. With a three-second +lease, the native observer detects expiration and exits with status 1, no +successful export and no forced shutdown. The normal default lease is 30 seconds. -Limits: -This is a developer preview for the exact compatibility configuration above. -It is not qualification of the current Isaac Capture native backend, physical -Pico/Quest tracking, hand/glove protocols, SO101, body tracking or real robots. -Four failed setup/integration attempts are retained before the successful fifth -attempt; these runs are not a reliability sample. No official SDK release or -main-branch merge is implied. +Evidence and checks: +Native datasets, command receipts, logs, source identities, independent numeric +verification and full recordings accompany the candidate. Selected-frame visual +inspection confirms approach, grasp, transfer and release in both views; this +is not exhaustive frame-by-frame visual model evaluation. +Unit controls cover invalid poses, stale/mismatched feedback, strict callback +results, driver ownership, expired heartbeat, completion and cleanup failures. +Final repository checks and package identities are in the release manifest. +The browser implementation and earlier MCAP replay are unchanged; no fresh MCAP +or browser-only transport-mock qualification is implied by these native runs. -Detailed evidence, source hashes, logs, videos and the final check receipts are -in the accompanying release evidence archive and SHA256 manifest. +Limits and retained failures: +This is validation on a prepared host, not a clean-machine installation study. +Host-specific browser test flags bypassed certificate and sandbox enforcement; +the documented customer setup requires a trusted certificate and normal sandbox. +A missing optional dependency in the test harness, an incorrect frame-array +assumption and native Kit process teardown exposed three failed attempts. Those +failures are retained; the latter two led to regression tests and a supervised +observer. The successful stacking wheel differs from the subsequent customer +wheel only by removal of an unused Python import in observer_process.py. +Repeated success/reliability, arbitrary robot/task adapters, real robots, +physical Pico/Quest sensing and current Isaac Capture 1.6 native compatibility +are not qualified. Earlier three-episode footage demonstrates the preceding +preview, not repetition testing of the new session API. +No official SDK release or upstream/main merge is implied. diff --git a/examples/synthetic_xr/pyproject.toml b/examples/synthetic_xr/pyproject.toml index 65dd418271..0f9282249c 100644 --- a/examples/synthetic_xr/pyproject.toml +++ b/examples/synthetic_xr/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "hatchling.build" [project] name = "isaaccapture-examples-synthetic-xr" -version = "0.1.0" +version = "0.2.0" description = "Synthetic XR Teleoperation desktop examples" readme = "README.md" license = "Apache-2.0" diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/browser_command.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/browser_command.py index 6b67b66e77..c43eeb9b92 100644 --- a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/browser_command.py +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/browser_command.py @@ -5,143 +5,175 @@ import atexit import json +import math from pathlib import Path import time -from urllib.parse import urlsplit -from urllib.request import urlopen +from urllib.parse import parse_qsl, urlsplit +from urllib.request import ProxyHandler, build_opener import uuid import websocket -_cdp_url = "http://127.0.0.1:9222" -_feedback_url = "http://127.0.0.1:8765/state" -_socket = None -_sequence = 0 -_output = None - -def configure(cdp_url, feedback_url, output=None): - global _cdp_url, _feedback_url, _output - for value in (cdp_url, feedback_url): - parsed = urlsplit(value) - if parsed.scheme != "http" or parsed.hostname not in { - "localhost", - "127.0.0.1", - "::1", - }: - raise ValueError( - "Example control and observation endpoints must use loopback HTTP" +class BrowserClient: + """One loopback browser connection and fresh observation stream per driver.""" + + def __init__( + self, + cdp_url="http://127.0.0.1:9222", + feedback_url="http://127.0.0.1:8765/state", + output=None, + ): + for value in (cdp_url, feedback_url): + parsed = urlsplit(value) + if ( + parsed.scheme != "http" + or parsed.hostname not in {"localhost", "127.0.0.1", "::1"} + or parsed.username + or parsed.password + or parsed.fragment + ): + raise ValueError( + "Control and observation endpoints must use loopback HTTP without credentials" + ) + self.cdp_url, self.feedback_url = cdp_url.rstrip("/"), feedback_url + self.output = None if output is None else Path(output) / "commands" + if self.output is not None: + self.output.mkdir(parents=True, exist_ok=True) + self._socket = None + self._sequence = 0 + + @staticmethod + def _read_json(url): + # Loopback control must never be routed through an ambient corporate proxy. + with build_opener(ProxyHandler({})).open(url, timeout=5) as response: + return json.load(response) + + def close(self): + if self._socket is not None: + self._socket.close() + self._socket = None + + def state(self, max_age=5): + result = self._read_json(self.feedback_url) + age = time.time() - result.get("timestamp", 0) + if not 0 <= age <= max_age: + raise RuntimeError("Simulator observation is stale or from the future") + return result + + def command(self, action, timeout=20, **parameters): + if not math.isfinite(timeout) or not 0 < timeout <= 300: + raise ValueError("Command timeout must be in (0, 300] wall-clock seconds") + if self._socket is None: + pages = [ + page + for page in self._read_json(self.cdp_url + "/json/list") + if page.get("type") == "page" + and dict(parse_qsl(urlsplit(page.get("url", "")).query)).get( + "syntheticXR" + ) + == "1" + ] + if len(pages) != 1: + raise RuntimeError( + "Open exactly one Synthetic XR client tab in the debugging browser" + ) + self._socket = websocket.create_connection( + pages[0]["webSocketDebuggerUrl"], timeout=timeout, suppress_origin=True ) - _cdp_url, _feedback_url = cdp_url.rstrip("/"), feedback_url - if output is not None: - _output = Path(output) / "commands" - _output.mkdir(parents=True, exist_ok=True) - - -def _read_json(url): - with urlopen(url, timeout=5) as response: - return json.load(response) + payload = {"action": action, **parameters} + expression = ( + """(c => { + const api = window.syntheticXR; + if (!api) throw new Error('Synthetic XR API is not ready'); + if (c.action === 'pose') { + const update = {}; + if (c.position) update.position = c.position; + if (c.quaternion) update.quaternion = c.quaternion; + if (c.buttons && c.buttons.trigger !== undefined) update.trigger = c.buttons.trigger; + if (c.buttons && c.buttons.squeeze !== undefined) update.grip = c.buttons.squeeze; + api.setController(c.hand || 'right', update); + } else if (c.action === 'head') { + const update = {}; + if (c.position) update.position = c.position; + if (c.quaternion) update.quaternion = c.quaternion; + api.setHead(update); + } else if (c.action === 'start') { + const button = document.getElementById('synthetic-xr-start-teleop'); + if (!button || button.disabled) throw new Error('Connect the client before starting teleoperation'); + button.click(); + } else if (c.action === 'stop') { + api.stop(); + } else if (c.action !== 'inspect') { + throw new Error('Unsupported synthetic input command'); + } + if (c.action === 'inspect' && (!api.lastFrame || performance.now() - api.lastFrame.time > 1000)) + throw new Error('No fresh WebXR frame; connect the client first'); + return {state: api.getState(), frame: api.lastFrame}; + })(""" + + json.dumps(payload, allow_nan=False) + + ")" + ) + self._sequence += 1 + self._socket.send( + json.dumps( + { + "id": self._sequence, + "method": "Runtime.evaluate", + "params": { + "expression": expression, + "returnByValue": True, + "awaitPromise": True, + }, + } + ) + ) + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + self._socket.settimeout(max(0.01, deadline - time.monotonic())) + message = json.loads(self._socket.recv()) + if message.get("id") != self._sequence: + continue + result = message.get("result", {}) + if "error" in message or "exceptionDetails" in result: + raise RuntimeError( + "Browser rejected synthetic input: " + json.dumps(message) + ) + receipt = { + "id": uuid.uuid4().hex, + "timestamp": time.time(), + "ok": True, + "command": payload, + "browser": result["result"]["value"], + } + if self.output is not None: + (self.output / ("command-" + receipt["id"] + ".json")).write_text( + json.dumps(receipt, indent=2) + ) + return receipt + raise TimeoutError("Browser did not acknowledge synthetic input") -def close(): - global _socket - if _socket is not None: - _socket.close() - _socket = None +_default = BrowserClient() -atexit.register(close) +def configure(cdp_url, feedback_url, output=None): + global _default + replacement = BrowserClient(cdp_url, feedback_url, output) + _default.close() + _default = replacement def command(action, timeout=20, **parameters): - global _socket, _sequence - if _socket is None: - pages = [ - page - for page in _read_json(_cdp_url + "/json/list") - if page.get("type") == "page" and "syntheticXR=1" in page.get("url", "") - ] - if len(pages) != 1: - raise RuntimeError( - "Open exactly one Synthetic XR client tab in the debugging browser" - ) - _socket = websocket.create_connection( - pages[0]["webSocketDebuggerUrl"], timeout=timeout, suppress_origin=True - ) - payload = {"action": action, **parameters} - expression = ( - """(c => { - const api = window.syntheticXR; - if (!api) throw new Error('Synthetic XR API is not ready'); - if (c.action === 'pose') { - const update = {}; - if (c.position) update.position = c.position; - if (c.quaternion) update.quaternion = c.quaternion; - if (c.buttons && c.buttons.trigger !== undefined) update.trigger = c.buttons.trigger; - if (c.buttons && c.buttons.squeeze !== undefined) update.grip = c.buttons.squeeze; - api.setController(c.hand || 'right', update); - } else if (c.action === 'head') { - const update = {}; - if (c.position) update.position = c.position; - if (c.quaternion) update.quaternion = c.quaternion; - api.setHead(update); - } else if (c.action === 'start') { - const button = document.getElementById('synthetic-xr-start-teleop'); - if (!button || button.disabled) throw new Error('Connect the client before starting teleoperation'); - button.click(); - } else if (c.action === 'stop') { - api.stop(); - } else if (c.action !== 'inspect') { - throw new Error('Unsupported synthetic input command'); - } - if (c.action === 'inspect' && (!api.lastFrame || performance.now() - api.lastFrame.time > 1000)) - throw new Error('No fresh WebXR frame; connect the client first'); - return {state: api.getState(), frame: api.lastFrame}; - })(""" - + json.dumps(payload, allow_nan=False) - + ")" - ) - _sequence += 1 - _socket.send( - json.dumps( - { - "id": _sequence, - "method": "Runtime.evaluate", - "params": { - "expression": expression, - "returnByValue": True, - "awaitPromise": True, - }, - } - ) - ) - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - message = json.loads(_socket.recv()) - if message.get("id") != _sequence: - continue - result = message.get("result", {}) - if "error" in message or "exceptionDetails" in result: - raise RuntimeError( - "Browser rejected synthetic input: " + json.dumps(message) - ) - receipt = { - "id": uuid.uuid4().hex, - "timestamp": time.time(), - "ok": True, - "command": payload, - "browser": result["result"]["value"], - } - if _output is not None: - (_output / ("command-" + receipt["id"] + ".json")).write_text( - json.dumps(receipt, indent=2) - ) - return receipt - raise TimeoutError("Browser did not acknowledge synthetic input") + return _default.command(action, timeout, **parameters) def state(max_age=5): - result = _read_json(_feedback_url) - if time.time() - result.get("timestamp", 0) > max_age: - raise RuntimeError("Simulator observation is stale") - return result + return _default.state(max_age) + + +def close(): + _default.close() + + +atexit.register(close) diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py index 799c9850a2..ab3472ef92 100644 --- a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py @@ -4,7 +4,7 @@ """Observe native Franka recording without replacing its retargeting pipeline. Robot actions come exclusively through the native WebXR/CloudXR device. -Only read-only feedback, visual presentation and orderly shutdown hooks are added. +Observation, optional task success and lifecycle hooks preserve native motion and export. """ import argparse @@ -18,27 +18,88 @@ import sys import threading import time +from types import SimpleNamespace + +from .task_hooks import check_driver_lease, evaluate_success, load_success_hook +from .observer_process import supervise, write_snapshot import numpy as np from scipy.spatial.transform import Rotation -parser = argparse.ArgumentParser(add_help=False) +# Help must work before required paths are checked or any native worker starts. +parser = argparse.ArgumentParser( + description="Observe Franka and supervise native episode recording.", + epilog="Additional arguments are forwarded to Lab scripts/tools/record_demos.py; " + "run that script with --help for native task options.", +) parser.add_argument("--lab-root", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) parser.add_argument("--feedback-port", type=int, default=8765) +parser.add_argument( + "--objects", + nargs="+", + default=["cube_1", "cube_2", "cube_3"], + help="Scene object names exposed in observations", +) +parser.add_argument( + "--success", + help="Trusted module:function accepting one observation and returning bool", +) +parser.add_argument( + "--driver-timeout", + type=float, + default=30, + help="Stop after this many seconds without an attached task driver heartbeat", +) +parser.add_argument("--worker-run-id", help=argparse.SUPPRESS) +parser.add_argument( + "--session-timeout", + type=float, + default=900, + help="Maximum total observer runtime in seconds", +) options, remaining = parser.parse_known_args() +if not np.isfinite(options.session_timeout) or options.session_timeout <= 0: + parser.error("Session timeout must be positive and finite") +if options.worker_run_id is None: + raise SystemExit( + supervise(options.output.resolve(), sys.argv[1:], options.session_timeout) + ) +if not np.isfinite(options.driver_timeout) or options.driver_timeout <= 0: + parser.error("Driver timeout must be positive and finite") +if len(set(options.objects)) != len(options.objects): + parser.error("Object names must be unique") +success_callback, success_identity = ( + (None, "native task") + if options.success is None + else load_success_hook(options.success) +) ROOT = options.output.resolve() REFERENCE = options.lab_root.resolve() / "scripts/tools/record_demos.py" if not REFERENCE.is_file(): raise FileNotFoundError(REFERENCE) +if ( + json.loads((ROOT / "supervisor.json").read_text())["run_id"] + != options.worker_run_id +): + raise RuntimeError("Worker does not belong to this supervisor") sys.argv = [str(REFERENCE), *remaining] spec = importlib.util.spec_from_file_location("synthetic_xr_record_demos", REFERENCE) native = importlib.util.module_from_spec(spec) sys.modules[spec.name] = native spec.loader.exec_module(native) -state = {"phase": "initializing", "steps": 0, "success_steps": 0} +state = { + "schema_version": 1, + "run_id": options.worker_run_id, + "coordinate_convention": "metres; world Z-up; quaternion xyzw", + "success_hook": success_identity, + "episode_id": 0, + "phase": "initializing", + "steps": 0, + "success_steps": 0, +} snapshot = json.dumps(state).encode() last_publish = 0.0 environment = None @@ -46,7 +107,6 @@ home = None clutch_node = None run_dir = ROOT -run_dir.mkdir(parents=True, exist_ok=False) trace = (run_dir / "sim-feedback.jsonl").open("x", buffering=1) state["run_dir"] = str(run_dir) @@ -91,7 +151,7 @@ def publish(force=False): env.scene[name].data.root_lin_vel_w ), } - for name in ("cube_1", "cube_2", "cube_3") + for name in options.objects }, "exported_successful_episodes": env.recorder_manager.exported_successful_episode_count, "configured_home_base_T_ee": home.tolist(), @@ -141,6 +201,22 @@ def log_message(self, *args): def create_environment_config(*args, **kwargs): cfg, success, use_capture = original_config(*args, **kwargs) + if not use_capture: + raise RuntimeError( + "Synthetic XR requires the native Isaac Teleop task pipeline" + ) + if success_callback is not None: + + def custom_success(env): + import torch + + return torch.tensor( + [evaluate_success(success_callback, state)], + dtype=torch.bool, + device=env.device, + ) + + success = SimpleNamespace(func=custom_success, params={}) if os.environ.get("SYNTHETIC_XR_VISIBLE") == "1": from isaaclab_visualizers.kit import KitVisualizerCfg @@ -192,6 +268,7 @@ def measure_home(): def reset(*args, **kwargs): result = original_reset(*args, **kwargs) + state["episode_id"] += 1 measure_home() return result @@ -247,6 +324,17 @@ def render(*args, **kwargs): return result env.sim.render = render + original_close = env.close + + def close(): + result = original_close() + state.update( + native_environment_closed=True, phase="native_closed", timestamp=time.time() + ) + write_snapshot(ROOT, state) + return result + + env.close = close publish(force=True) return env @@ -266,6 +354,9 @@ def setup_device(*args, **kwargs): original_advance = teleop.advance def advance(*args, **kwargs): + check_driver_lease( + run_dir / "driver-heartbeat.json", state["run_id"], options.driver_timeout + ) if (run_dir / "stop").exists(): raise KeyboardInterrupt("Synthetic XR example requested stop") return original_advance(*args, **kwargs) @@ -279,10 +370,14 @@ def advance(*args, **kwargs): def process_success(env, term, count): + before = state.get("exported_successful_episodes", 0) result = original_success(env, term, count) state["success_steps"] = result[0] state["success_reset_needed"] = result[1] - publish() + publish(force=True) + exported = state.get("exported_successful_episodes", 0) + if exported > before: + (ROOT / f"exported-episode-{exported}.json").write_bytes(snapshot) return result @@ -292,9 +387,24 @@ def process_success(env, term, count): native.process_success_condition = process_success server = ThreadingHTTPServer(("127.0.0.1", options.feedback_port), Feedback) threading.Thread(target=server.serve_forever, daemon=True).start() +completion_error = None try: native.main() +except BaseException as error: + completion_error = repr(error) + raise finally: + # Preserve final counters after native cleanup without touching destroyed tensors. + state.update( + phase="worker_finished", + timestamp=time.time(), + completion_error=completion_error, + ) + snapshot = json.dumps(state, allow_nan=False).encode() + temporary = ROOT / "sim-state.tmp" + temporary.write_bytes(snapshot) + temporary.replace(ROOT / "sim-state.json") + trace.write(snapshot.decode() + "\n") server.shutdown() server.server_close() trace.close() diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observer_process.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observer_process.py new file mode 100644 index 0000000000..253fbcc941 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observer_process.py @@ -0,0 +1,113 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Retain worker exit status even when Kit terminates its Python process.""" + +import json +import os +import signal +import subprocess +import sys +import time +import uuid + + +def write_snapshot(root, value): + temporary = root / "sim-state.tmp" + temporary.write_text(json.dumps(value, allow_nan=False)) + temporary.replace(root / "sim-state.json") + + +def completion_snapshot(root, run_id, returncode, interrupted=False): + path = root / "sim-state.json" + value = json.loads(path.read_text()) if path.exists() else {"run_id": run_id} + errors = [] + if value.get("run_id") != run_id: + errors.append("Worker observation identity mismatch") + if returncode != 0 or interrupted: + errors.append( + f"Worker exited with status {returncode}; interrupted={interrupted}" + ) + if not value.get("native_environment_closed"): + errors.append("Native environment did not confirm closure") + if value.get("completion_error"): + errors.append(value["completion_error"]) + value.update( + phase="completed", + timestamp=time.time(), + worker_returncode=returncode, + completion_error="; ".join(errors) or None, + ) + return value + + +def supervise(root, arguments, timeout): + root.mkdir(parents=True, exist_ok=False) + run_id = uuid.uuid4().hex + command = [ + sys.executable, + "-u", + "-m", + "isaaccapture_examples.synthetic_xr.franka.observe_record_demos", + "--worker-run-id", + run_id, + *arguments, + ] + (root / "supervisor.json").write_text( + json.dumps({"run_id": run_id, "command": command}, indent=2) + "\n" + ) + process = subprocess.Popen(command, start_new_session=True) + interrupted = False + + def forward(signum, frame): + nonlocal interrupted + interrupted = True + if process.poll() is None: + os.killpg(process.pid, signal.SIGINT) + + previous = { + sig: signal.signal(sig, forward) for sig in (signal.SIGINT, signal.SIGTERM) + } + forced = [] + try: + deadline = time.monotonic() + timeout + while ( + process.poll() is None and not interrupted and time.monotonic() < deadline + ): + time.sleep(0.1) + if process.poll() is None: + interrupted = True + for sig, grace in ( + (signal.SIGINT, 20), + (signal.SIGTERM, 10), + (signal.SIGKILL, 5), + ): + forced.append(signal.Signals(sig).name) + os.killpg(process.pid, sig) + try: + process.wait(timeout=grace) + break + except subprocess.TimeoutExpired: + continue + code = process.wait(timeout=5) + value = completion_snapshot(root, run_id, code, interrupted) + value["shutdown_signals"] = forced + write_snapshot(root, value) + with (root / "sim-feedback.jsonl").open("a") as trace: + trace.write(json.dumps(value, allow_nan=False) + "\n") + (root / "worker-exit.json").write_text( + json.dumps( + { + "run_id": run_id, + "returncode": code, + "interrupted": interrupted, + "shutdown_signals": forced, + }, + indent=2, + ) + + "\n" + ) + return 0 if value["completion_error"] is None else 1 + finally: + for sig, handler in previous.items(): + signal.signal(sig, handler) diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py new file mode 100644 index 0000000000..74d3cd8bc3 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py @@ -0,0 +1,324 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Task-facing Franka tool commands over the native Synthetic XR path.""" + +import json +from pathlib import Path +import time +import math +import os +from urllib.error import URLError + +import numpy as np +from scipy.spatial.transform import Rotation + +from .browser_command import BrowserClient +from .xr_controller_bridge import XRControllerBridge, play, wait_steps + +TOOL_OFFSET = np.array([0.0, 0.0, 0.1034]) + + +def validate_observation(value): + if value.get("schema_version") != 1: + raise ValueError( + "Expected Synthetic XR observation schema_version=1; start the matching observer" + ) + if ( + not value.get("run_id") + or value.get("coordinate_convention") != "metres; world Z-up; quaternion xyzw" + ): + raise ValueError( + "Missing observation identity or incompatible coordinate convention" + ) + if ( + not np.isfinite(value.get("simulation_dt", 0)) + or value.get("simulation_dt", 0) <= 0 + ): + raise ValueError("Invalid simulation step duration") + frame_count = None + for name, width in (("ee_positions_world", 3), ("ee_quaternions_world", 4)): + array = np.asarray(value[name]) + # Native ee_frame includes the tool followed by finger targets; index zero is the tool. + if ( + array.ndim != 3 + or array.shape[0] != 1 + or array.shape[1] < 1 + or array.shape[2] != width + or not np.isfinite(array).all() + ): + raise ValueError("Invalid observation field: " + name) + if frame_count is not None and array.shape[1] != frame_count: + raise ValueError("Position and orientation target counts differ") + frame_count = array.shape[1] + for obj in value["cubes"].values(): + for name, width in ( + ("position_world", 3), + ("quaternion_world", 4), + ("linear_velocity_world", 3), + ): + array = np.asarray(obj[name]) + if array.shape != (1, width) or not np.isfinite(array).all(): + raise ValueError("Invalid object observation: " + name) + return value + + +class ObserverUnavailable(RuntimeError): + """The supervisor has not yet confirmed worker completion.""" + + +def check_timeout(timeout): + if not math.isfinite(timeout) or not 0 < timeout <= 300: + raise ValueError("Timeout must be in (0, 300] wall-clock seconds") + + +class FrankaTaskSession: + """Attach to one local observer; release input and stop it on context exit.""" + + def __init__( + self, + *, + observation_dir, + output, + cdp_url="http://127.0.0.1:9222", + feedback_url="http://127.0.0.1:8765/state", + stop_observer=True, + ): + self.observation_dir = Path(observation_dir).resolve() + self.output = Path(output).resolve() + self.client = BrowserClient(cdp_url, feedback_url) + self.controller = None + self.run_id = None + self.stop_observer = stop_observer + self._trace = None + self._entered = False + + def state(self, max_age=5): + try: + value = self.client.state(max_age) + except (URLError, OSError): + value = json.loads((self.observation_dir / "sim-state.json").read_text()) + # Only an orderly terminal snapshot can outlive its HTTP server. + if value.get("phase") != "completed" or value.get("completion_error"): + raise ObserverUnavailable( + "Observer disconnected before orderly completion" + ) from None + value = validate_observation(value) + if value.get("phase") == "completed" and value.get("completion_error"): + raise RuntimeError("Observer failed: " + value["completion_error"]) + if Path(value["run_dir"]).resolve() != self.observation_dir: + raise RuntimeError("Feedback belongs to a different observation directory") + if self.run_id is not None and value["run_id"] != self.run_id: + raise RuntimeError("Observer restarted during the task") + self._heartbeat() + return value + + def _heartbeat(self): + if self.run_id is not None: + temporary = self.observation_dir / "driver-heartbeat.tmp" + temporary.write_text( + json.dumps({"run_id": self.run_id, "monotonic": time.monotonic()}) + ) + temporary.replace(self.observation_dir / "driver-heartbeat.json") + + def command(self, *args, **kwargs): + self._heartbeat() + return self.client.command(*args, **kwargs) + + def __enter__(self): + if self._entered: + raise RuntimeError("Task sessions cannot be entered twice") + self._entered = True + self.output.mkdir(parents=True, exist_ok=False) + self.client.output = self.output / "commands" + self.client.output.mkdir() + self._trace = (self.output / "stages.jsonl").open("x", buffering=1) + try: + observation = self.state() + if observation.get("phase") == "completed" or observation.get( + "exported_successful_episodes", 0 + ): + raise RuntimeError( + "Start a fresh observer before opening a task session" + ) + # Reserve before sending input; another driver must not control this observer. + with (self.observation_dir / "driver-owner.json").open("x") as owner: + json.dump({"run_id": observation["run_id"], "pid": os.getpid()}, owner) + self.run_id = observation["run_id"] + self.command("inspect") + self.command( + "head", quaternion=Rotation.from_euler("x", -0.55).as_quat().tolist() + ) + self.controller = XRControllerBridge( + client=self, acknowledgement_path=self.output / "controller-acks.jsonl" + ) + if not observation.get("control", {}).get("is_active"): + play(client=self) + self.controller.target_base( + self.controller.target[:3, 3], self.controller.target[:3, :3] + ) + self.mark("started") + return self + except BaseException as error: + self.__exit__(type(error), error, error.__traceback__) + raise + + def observe(self): + return self.state() + + def mark(self, name): + observation = self.observe() + self._trace.write( + json.dumps({"stage": name, "state": observation}, allow_nan=False) + "\n" + ) + return observation + + def wait(self, seconds, timeout=30): + check_timeout(timeout) + if not np.isfinite(seconds) or not 0 < seconds <= 120: + raise ValueError("Wait duration must be in (0, 120] simulation seconds") + return wait_steps( + max(1, int(np.ceil(seconds / self.state()["simulation_dt"]))), + timeout, + client=self, + ) + + def move_tool(self, position, quaternion, *, seconds=2): + """Move the ee_frame in world metres, with a unit xyzw quaternion.""" + point, q = ( + np.asarray(position, dtype=float), + np.asarray(quaternion, dtype=float), + ) + if ( + point.shape != (3,) + or not np.isfinite(point).all() + or q.shape != (4,) + or not np.isfinite(q).all() + or not np.isclose(np.linalg.norm(q), 1, atol=1e-5) + ): + raise ValueError( + "Expected finite world position[3] and unit xyzw quaternion[4]" + ) + rotation = Rotation.from_quat(q).as_matrix() + self.controller.move_world( + point - rotation @ TOOL_OFFSET, rotation, seconds=seconds + ) + for _ in range(4): + current = self.state() + if current.get("exported_successful_episodes", 0): + return current + error = point - np.asarray(current["ee_positions_world"])[0, 0] + if np.linalg.norm(error) < 0.002: + return current + target = ( + np.linalg.inv(self.controller.base_to_world) @ self.controller.target + ) + self.controller.move_world( + target[:3, 3] + np.clip(error, -0.01, 0.01), rotation, seconds=0.6 + ) + if ( + np.linalg.norm(point - np.asarray(self.state()["ee_positions_world"])[0, 0]) + > 0.01 + ): + raise RuntimeError( + "Tool did not converge within 0.01 m of its requested target" + ) + return self.state() + + def set_gripper(self, *, closed): + if type(closed) is not bool: + raise TypeError("closed must be bool") + if self.state().get("exported_successful_episodes", 0): + raise RuntimeError("Episode already completed") + self.controller.target_base( + self.controller.target[:3, 3], + self.controller.target[:3, :3], + trigger=float(closed), + ) + + def wait_for_export(self, timeout=30): + """Wait for native recorder confirmation; never force a success flag.""" + check_timeout(timeout) + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + try: + observation = self.state() + except ObserverUnavailable: + time.sleep(0.05) + continue + if ( + observation.get("exported_successful_episodes", 0) > 0 + and observation.get("phase") == "completed" + ): + self.mark("native_export") + (self.output / "result.json").write_text( + json.dumps( + { + "status": "PASS", + "run_id": self.run_id, + "native_exports": observation[ + "exported_successful_episodes" + ], + "success_hook": observation.get( + "success_hook", "native task" + ), + }, + indent=2, + ) + + "\n" + ) + return observation + if observation.get("phase") == "completed": + raise RuntimeError( + "Observer completed without a native successful episode" + ) + time.sleep(0.05) + raise TimeoutError("Native success condition did not export an episode") + + def __exit__(self, error_type, error, traceback): + cleanup_error = None + try: + if self.run_id is not None: + self.command("stop") + except Exception as failure: + cleanup_error = failure + finally: + if self.controller is not None: + self.controller.ack_trace.close() + self.client.close() + if self._trace is not None: + self._trace.close() + if self.run_id is not None and self.stop_observer: + try: + current = json.loads( + (self.observation_dir / "sim-state.json").read_text() + ) + if ( + current.get("run_id") == self.run_id + and current.get("phase") != "completed" + ): + (self.observation_dir / "stop").touch() + except Exception as failure: + cleanup_error = cleanup_error or failure + if error is not None or cleanup_error is not None: + (self.output / "result.json").write_text( + json.dumps( + { + "status": "FAIL", + "error": repr(error), + "cleanup_error": repr(cleanup_error), + }, + indent=2, + ) + + "\n" + ) + (self.output / "failure.json").write_text( + json.dumps( + {"error": repr(error), "cleanup_error": repr(cleanup_error)}, + indent=2, + ) + + "\n" + ) + if cleanup_error is not None and error is None: + raise cleanup_error + return False diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py index 30c639587f..92b79af0d4 100644 --- a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py @@ -11,140 +11,101 @@ import numpy as np from scipy.spatial.transform import Rotation -from .browser_command import command, configure, state -from .xr_controller_bridge import XRControllerBridge, play, wait_steps +from .session import FrankaTaskSession -parser = argparse.ArgumentParser() -parser.add_argument( - "--stop-after", choices=["approach", "grasp", "stack"], default="stack" -) -parser.add_argument("--cdp-url", default="http://127.0.0.1:9222") -parser.add_argument("--feedback-url", default="http://127.0.0.1:8765/state") -parser.add_argument("--output", type=Path, required=True) -args = parser.parse_args() -configure(args.cdp_url, args.feedback_url, args.output) -run = args.output / ("stack-logic-" + str(time.time_ns())) -run.mkdir(parents=True) -trace = (run / "stages.jsonl").open("x", buffering=1) -TOOL_OFFSET = np.array( - [0.0, 0.0, 0.1034] -) # Native Franka ee_frame offset from panda_hand. - -def mark(stage): - s = state() - trace.write(json.dumps({"stage": stage, "state": s}) + "\n") - print( - stage, - json.dumps( - { - "steps": s["steps"], - "ee": s["ee_positions_world"][0][0], - "joints": s["joints"][0], - "cubes": {k: v["position_world"][0] for k, v in s["cubes"].items()}, - } - ), - flush=True, - ) - return s - - -def cube(name): - return np.asarray(state()["cubes"][name]["position_world"])[0] - - -def move_tool(controller, point, rotation, seconds=2): - point = np.asarray(point) - controller.move_world(point - rotation @ TOOL_OFFSET, rotation, seconds=seconds) - for _ in range(4): - if state().get("exported_successful_episodes", 0): - return - error = point - np.asarray(state()["ee_positions_world"])[0, 0] - if np.linalg.norm(error) < 0.002: - return - target = np.linalg.inv(controller.base_to_world) @ controller.target - controller.move_world( - target[:3, 3] + np.clip(error, -0.01, 0.01), rotation, seconds=0.6 - ) - if np.linalg.norm(point - np.asarray(state()["ee_positions_world"])[0, 0]) > 0.01: - raise RuntimeError("Gripper did not converge to the requested approach point") - - -def transfer(controller, name, support): - start = cube(name) - yaw = Rotation.from_quat(state()["cubes"][name]["quaternion_world"][0]).as_euler( +def transfer(session, name, support, stop_after): + initial = session.observe() + start = np.array(initial["cubes"][name]["position_world"][0]) + yaw = Rotation.from_quat(initial["cubes"][name]["quaternion_world"][0]).as_euler( "xyz" )[2] orientation = ( Rotation.from_euler("z", yaw) * Rotation.from_euler("x", np.pi) - ).as_matrix() - ee = np.asarray(state()["ee_positions_world"])[0, 0] - move_tool(controller, [ee[0], ee[1], max(0.23, ee[2])], orientation, seconds=2) - move_tool(controller, start + [0, 0, 0.14], orientation, seconds=3) - mark(name + "_above") - if args.stop_after == "approach": + ).as_quat() + ee = np.array(initial["ee_positions_world"][0][0]) + session.move_tool([ee[0], ee[1], max(0.23, ee[2])], orientation) + session.move_tool(start + [0, 0, 0.14], orientation, seconds=3) + session.mark(name + "_above") + if stop_after == "approach": return False - move_tool(controller, start + [0, 0, 0.003], orientation, seconds=3) - mark(name + "_at_grasp") - controller.target_base( - controller.target[:3, 3], controller.target[:3, :3], trigger=1.0 - ) - wait_steps(count=20) - mark(name + "_closed") - move_tool(controller, start + [0, 0, 0.16], orientation, seconds=3) - lifted = mark(name + "_lifted") - if cube(name)[2] - start[2] < 0.08: + session.move_tool(start + [0, 0, 0.003], orientation, seconds=3) + session.set_gripper(closed=True) + session.wait(1) + session.move_tool(start + [0, 0, 0.16], orientation, seconds=3) + lifted = session.mark(name + "_lifted") + cube = np.array(lifted["cubes"][name]["position_world"][0]) + if cube[2] - start[2] < 0.08: raise RuntimeError(f"{name} did not lift") - if args.stop_after == "grasp": + if stop_after == "grasp": return False - goal = cube(support) + [0, 0, 0.0468] - held_offset = cube(name) - np.asarray(lifted["ee_positions_world"])[0, 0] - move_tool(controller, goal - held_offset + [0, 0, 0.12], orientation, seconds=3) - move_tool(controller, goal - held_offset + [0, 0, 0.003], orientation, seconds=3) + goal = np.array(lifted["cubes"][support]["position_world"][0]) + [0, 0, 0.0468] + held_offset = cube - lifted["ee_positions_world"][0][0] + session.move_tool(goal - held_offset + [0, 0, 0.12], orientation, seconds=3) + session.move_tool(goal - held_offset + [0, 0, 0.003], orientation, seconds=3) for _ in range(3): - error = goal + [0, 0, 0.003] - cube(name) + current = session.observe() + if current.get("exported_successful_episodes", 0): + return True + error = goal + [0, 0, 0.003] - current["cubes"][name]["position_world"][0] if np.linalg.norm(error) < 0.003: break - target = np.linalg.inv(controller.base_to_world) @ controller.target - controller.move_world( - target[:3, 3] + np.clip(error, -0.01, 0.01), orientation, seconds=0.6 + session.move_tool( + np.array(current["ee_positions_world"][0][0]) + np.clip(error, -0.01, 0.01), + orientation, + seconds=0.6, ) - mark(name + "_placed") - controller.target_base( - controller.target[:3, 3], controller.target[:3, :3], trigger=0.0 - ) - wait_steps(count=5) - ee = np.asarray(state()["ee_positions_world"])[0, 0] - move_tool(controller, ee + [0, 0, 0.13], orientation, seconds=3) - wait_steps(count=30) - mark(name + "_released") - delta = cube(name) - cube(support) - if np.linalg.norm(delta[:2]) > 0.015 or abs(delta[2] - 0.0468) > 0.006: - raise RuntimeError(f"{name} did not settle on {support}: {delta.tolist()}") + session.mark(name + "_placed") + session.set_gripper(closed=False) + session.wait(0.25) + ee = np.array(session.observe()["ee_positions_world"][0][0]) + session.move_tool(ee + [0, 0, 0.13], orientation, seconds=3) + session.wait(1.5) + final = session.mark(name + "_released") + if not final.get("exported_successful_episodes", 0): + delta = ( + np.array(final["cubes"][name]["position_world"][0]) + - final["cubes"][support]["position_world"][0] + ) + if np.linalg.norm(delta[:2]) > 0.015 or abs(delta[2] - 0.0468) > 0.006: + raise RuntimeError(f"{name} did not settle on {support}: {delta.tolist()}") return True -controller = None -try: - command("head", quaternion=Rotation.from_euler("x", -0.55).as_quat().tolist()) - controller = XRControllerBridge() - if not state().get("control", {}).get("is_active"): - play() - controller.target_base(controller.target[:3, 3], controller.target[:3, :3]) - mark("started") - if transfer(controller, "cube_2", "cube_1"): - if transfer(controller, "cube_3", "cube_2"): - final = wait_steps(count=30) - if not final.get("exported_successful_episodes", 0): - raise RuntimeError( - "Stack sequence ended without native successful export" - ) - print("STACK_LOGIC_END", run, flush=True) -except Exception as error: - (run / "failure.txt").write_text(repr(error) + "\n") - print("STACK_LOGIC_FAILED", repr(error), flush=True) - raise -finally: - if controller is not None: - controller.release() - trace.close() +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--stop-after", choices=["approach", "grasp", "stack"], default="stack" + ) + parser.add_argument("--cdp-url", default="http://127.0.0.1:9222") + parser.add_argument("--feedback-url", default="http://127.0.0.1:8765/state") + parser.add_argument("--observation-dir", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + run = args.output / ("stack-logic-" + str(time.time_ns())) + with FrankaTaskSession( + observation_dir=args.observation_dir, + output=run, + cdp_url=args.cdp_url, + feedback_url=args.feedback_url, + ) as session: + if transfer(session, "cube_2", "cube_1", args.stop_after) and transfer( + session, "cube_3", "cube_2", args.stop_after + ): + final = session.wait_for_export() + print( + "STACK_LOGIC_END", + json.dumps( + { + "run": str(run), + "native_exports": final["exported_successful_episodes"], + } + ), + flush=True, + ) + else: + print("STACK_LOGIC_PARTIAL", args.stop_after, run, flush=True) + + +if __name__ == "__main__": + main() diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_hooks.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_hooks.py new file mode 100644 index 0000000000..d7bfa8dcb7 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_hooks.py @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Load a trusted application predicate without exposing the simulator object.""" + +import hashlib +import importlib +import inspect +import json +import math +import time +from pathlib import Path + + +def load_success_hook(specification): + module, separator, name = specification.partition(":") + if not separator or not module or not name.isidentifier(): + raise ValueError("Success hook must be an importable module:function") + callback = getattr(importlib.import_module(module), name) + if not callable(callback): + raise TypeError("Success hook is not callable") + inspect.signature(callback).bind({}) + source = Path(inspect.getfile(callback)) + identity = { + "entrypoint": specification, + "sha256": hashlib.sha256(source.read_bytes()).hexdigest(), + } + return callback, identity + + +def evaluate_success(callback, observation): + # A detached JSON snapshot prevents accidental mutation of live observation state. + result = callback(json.loads(json.dumps(observation, allow_nan=False))) + if type(result) is not bool: + raise TypeError("Success hook must return bool, not a truthy value") + return result + + +def check_driver_lease(path, run_id, timeout): + if not path.exists(): + return + lease = json.loads(path.read_text()) + age = time.monotonic() - lease["monotonic"] + if ( + lease.get("run_id") != run_id + or not math.isfinite(age) + or not 0 <= age <= timeout + ): + raise RuntimeError("Task driver lease expired or belongs to another observer") diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py index 19309048db..d36df9e39b 100644 --- a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py @@ -10,7 +10,7 @@ import numpy as np from scipy.spatial.transform import Rotation, Slerp -from .browser_command import command, state +from . import browser_command def transform(position, quaternion): @@ -20,11 +20,11 @@ def transform(position, quaternion): return result -def wait_steps(count=3, timeout=20): - first = state()["steps"] +def wait_steps(count=3, timeout=20, client=browser_command): + first = client.state()["steps"] deadline = time.monotonic() + timeout while time.monotonic() < deadline: - current = state() + current = client.state() if current["steps"] >= first + count or current.get( "exported_successful_episodes", 0 ): @@ -33,15 +33,16 @@ def wait_steps(count=3, timeout=20): raise TimeoutError("Native simulation did not advance") -def play(): +def play(client=browser_command): # Start through the existing client handler, not a layout-dependent controller ray. - command("start") - return wait_steps() + client.command("start") + return wait_steps(client=client) class XRControllerBridge: - def __init__(self): - initial = state() + def __init__(self, client=browser_command, acknowledgement_path=None): + self.client = client + initial = self.client.state() self.base_to_world = ( np.eye(4) if initial["base_T_world"] is None @@ -50,9 +51,9 @@ def __init__(self): self.base_to_anchor = self.base_to_world @ np.asarray(initial["world_T_anchor"]) # The native Franka task config applies +90 degrees intrinsic X to grip orientation. self.offset = Rotation.from_euler("X", 90, degrees=True).as_matrix() - command("pose", hand="right", buttons={"squeeze": 1, "trigger": 0}) + self.client.command("pose", hand="right", buttons={"squeeze": 1, "trigger": 0}) time.sleep(0.3) - frame = command("inspect")["browser"]["frame"] + frame = self.client.command("inspect")["browser"]["frame"] right = next(s for s in frame["sources"] if s["hand"] == "right") self.aim_to_grip = np.linalg.inv(transform(**right["aim"])) @ transform( **right["grip"] @@ -63,9 +64,11 @@ def __init__(self): initial["body_quaternions_world"][0][index], ) self.trigger = 0.0 - self.ack_trace = (Path(initial["run_dir"]) / "controller-acks.jsonl").open( - "a", buffering=1 - ) + self.ack_trace = ( + Path(acknowledgement_path) + if acknowledgement_path is not None + else Path(initial["run_dir"]) / "controller-acks.jsonl" + ).open("a", buffering=1) # Place the virtual controller at the current robot target before starting native physics. self.target_base(self.target[:3, 3], self.target[:3, :3], wait_receipt=False) @@ -78,6 +81,10 @@ def target_base(self, position, rotation, trigger=None, wait_receipt=True): or not np.isfinite(rot).all() ): raise ValueError("Invalid target pose") + if not np.allclose(rot.T @ rot, np.eye(3), atol=1e-6) or not np.isclose( + np.linalg.det(rot), 1.0, atol=1e-6 + ): + raise ValueError("Target rotation must be orthonormal with determinant +1") if np.linalg.norm(p) > 1.5: raise ValueError("Target outside declared Franka test workspace") self.trigger = self.trigger if trigger is None else float(trigger) @@ -91,7 +98,7 @@ def target_base(self, position, rotation, trigger=None, wait_receipt=True): @ base_grip @ np.linalg.inv(self.aim_to_grip) ) - result = command( + result = self.client.command( "pose", position=aim[:3, 3].tolist(), quaternion=Rotation.from_matrix(aim[:3, :3]).as_quat().tolist(), @@ -104,7 +111,7 @@ def target_base(self, position, rotation, trigger=None, wait_receipt=True): gripper = -1.0 if self.trigger > 0.5 else 1.0 deadline = time.monotonic() + 8 while time.monotonic() < deadline: - observed = state() + observed = self.client.state() if observed.get("exported_successful_episodes", 0): return result action = np.asarray(observed["last_action"])[0] @@ -140,6 +147,8 @@ def target_base(self, position, rotation, trigger=None, wait_receipt=True): ) def move_world(self, position, rotation=None, trigger=None, seconds=2): + if not np.isfinite(seconds) or not 0 < seconds <= 120: + raise ValueError("Movement duration must be in (0, 120] seconds") current_world = np.linalg.inv(self.base_to_world) @ self.target rotation = current_world[:3, :3] if rotation is None else rotation target = self.base_to_world @ transform( @@ -151,18 +160,23 @@ def move_world(self, position, rotation=None, trigger=None, seconds=2): [0, 1], Rotation.from_matrix([origin[:3, :3], target[:3, :3]]) ) for fraction in np.linspace(0, 1, steps + 1)[1:]: - if state().get("exported_successful_episodes", 0): - return state() + if self.client.state().get("exported_successful_episodes", 0): + return self.client.state() self.target_base( origin[:3, 3] * (1 - fraction) + target[:3, 3] * fraction, orientations(fraction).as_matrix(), trigger, ) - wait_steps(count=max(1, round(seconds / steps / state()["simulation_dt"]))) - return wait_steps() + wait_steps( + count=max( + 1, round(seconds / steps / self.client.state()["simulation_dt"]) + ), + client=self.client, + ) + return wait_steps(client=self.client) def release(self): try: - command("pose", buttons={"squeeze": 0, "trigger": self.trigger}) + self.client.command("pose", buttons={"squeeze": 0, "trigger": self.trigger}) finally: self.ack_trace.close() diff --git a/examples/synthetic_xr/tests/test_task_session.py b/examples/synthetic_xr/tests/test_task_session.py new file mode 100644 index 0000000000..cd7bf7c3c0 --- /dev/null +++ b/examples/synthetic_xr/tests/test_task_session.py @@ -0,0 +1,284 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Failure controls for application hooks and the loopback task boundary.""" + +import json +import subprocess +import sys +from pathlib import Path +from tempfile import TemporaryDirectory +import time +import unittest +from unittest.mock import Mock, patch +from urllib.error import URLError + +import numpy as np + +# Keep helper imports above fixtures to preserve import-only test collection. +from isaaccapture_examples.synthetic_xr.franka.observer_process import ( + completion_snapshot, +) +from isaaccapture_examples.synthetic_xr.franka.browser_command import BrowserClient +from isaaccapture_examples.synthetic_xr.franka.session import ( + FrankaTaskSession, + validate_observation, +) +from isaaccapture_examples.synthetic_xr.franka.task_hooks import ( + check_driver_lease, + evaluate_success, + load_success_hook, +) +from isaaccapture_examples.synthetic_xr.franka.xr_controller_bridge import ( + XRControllerBridge, +) + + +def observation(root): + return { + "schema_version": 1, + "run_id": "one", + "coordinate_convention": "metres; world Z-up; quaternion xyzw", + "run_dir": str(root), + "phase": "running", + "timestamp": time.time(), + "simulation_dt": 0.05, + "ee_positions_world": [[[0.4, 0, 0.2]]], + "ee_quaternions_world": [[[0, 0, 0, 1]]], + "cubes": { + "cube_1": { + "position_world": [[0.4, 0, 0.02]], + "quaternion_world": [[0, 0, 0, 1]], + "linear_velocity_world": [[0, 0, 0]], + } + }, + "exported_successful_episodes": 0, + } + + +class TaskTests(unittest.TestCase): + def test_observer_help_needs_no_lab_or_output(self): + result = subprocess.run( + [ + sys.executable, + "-m", + "isaaccapture_examples.synthetic_xr.franka.observe_record_demos", + "--help", + ], + check=False, # Assert the exit code below, retaining stderr on failure. + capture_output=True, + text=True, + timeout=10, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("--success", result.stdout) + self.assertIn("Additional arguments are forwarded", result.stdout) + + def test_hook_cannot_modify_original_and_must_return_bool(self): + original = {"nested": {"value": 4}} + + def callback(value): + value["nested"]["value"] = 9 + return False + + self.assertFalse(evaluate_success(callback, original)) + self.assertEqual(original["nested"]["value"], 4) + for result in (None, 1, "PASS", np.bool_(True), [False]): + with self.subTest(result=result), self.assertRaises(TypeError): + # Bind each negative-control value; deferred callbacks must not capture the loop cell. + evaluate_success(lambda _, result=result: result, original) + + def test_driver_lease_detects_crash_and_wrong_session(self): + with TemporaryDirectory() as directory: + path = Path(directory) / "heartbeat.json" + check_driver_lease(path, "one", 30) + path.write_text( + json.dumps({"run_id": "one", "monotonic": time.monotonic()}) + ) + check_driver_lease(path, "one", 30) + for identity, age in (("two", 0), ("one", 31), ("one", -10)): + path.write_text( + json.dumps( + {"run_id": identity, "monotonic": time.monotonic() - age} + ) + ) + with self.assertRaises(RuntimeError): + check_driver_lease(path, "one", 30) + + def test_hook_exception_propagates(self): + def broken(_): + raise RuntimeError("criterion failed") + + with self.assertRaisesRegex(RuntimeError, "criterion failed"): + evaluate_success(broken, {}) + with self.assertRaises(ValueError): + load_success_hook("not_a_module_function") + + def test_stale_future_and_missing_timestamp_rejected(self): + client = BrowserClient() + for timestamp in (0, time.time() - 60, time.time() + 60, float("nan")): + with ( + patch.object( + client, "_read_json", return_value={"timestamp": timestamp} + ), + self.assertRaises(RuntimeError), + ): + client.state() + + def test_endpoints_reject_remote_hosts_and_userinfo(self): + for url in ( + "http://example.com:9222", + "https://localhost", + "http://user:secret@localhost", + "http://localhost#fragment", + ): + with self.subTest(url=url), self.assertRaises(ValueError): + BrowserClient(url) + + def test_sessions_do_not_share_browser_connection(self): + first, second = BrowserClient(), BrowserClient() + first._socket = Mock() + second.close() + first._socket.close.assert_not_called() + first.close() + self.assertIsNone(first._socket) + + def test_invalid_feedback_shape_and_nan_rejected(self): + for bad in ([[[1, 2]]], [[[1, 2, float("nan")]]]): + value = observation(Path("/tmp/observer")) + value["ee_positions_world"] = bad + with self.assertRaises(ValueError): + validate_observation(value) + + def test_native_tool_and_finger_frames_are_accepted(self): + value = observation(Path("/tmp/observer")) + value["ee_positions_world"] = [ + [[0.4, 0, 0.2], [0.4, 0.04, 0.2], [0.4, -0.04, 0.2]] + ] + value["ee_quaternions_world"] = [[[0, 0, 0, 1]] * 3] + self.assertEqual( + validate_observation(value)["ee_positions_world"][0][0], [0.4, 0, 0.2] + ) + value["ee_quaternions_world"] = [[[0, 0, 0, 1]]] + with self.assertRaisesRegex(ValueError, "counts differ"): + validate_observation(value) + + def test_terminal_snapshot_requires_matching_identity_and_clean_completion(self): + with TemporaryDirectory() as directory: + root = Path(directory) + session = FrankaTaskSession(observation_dir=root, output=root / "task") + session.run_id = "one" + session.client.state = Mock(side_effect=URLError("disconnected")) + for phase, identity, error, passes in ( + ("running", "one", None, False), + ("completed", "two", None, False), + ("completed", "one", "failure", False), + ("completed", "one", None, True), + ): + value = observation(root) + value.update(phase=phase, run_id=identity, completion_error=error) + (root / "sim-state.json").write_text(json.dumps(value)) + if passes: + self.assertEqual(session.state()["phase"], "completed") + else: + with self.assertRaises(RuntimeError): + session.state() + + def test_export_requires_native_completion_and_never_writes_success(self): + with TemporaryDirectory() as directory: + root = Path(directory) + session = FrankaTaskSession(observation_dir=root, output=root / "task") + value = observation(root) + value.update(phase="completed") + session.state = Mock(return_value=value) + with self.assertRaisesRegex(RuntimeError, "without a native successful"): + session.wait_for_export() + self.assertFalse((root / "task/result.json").exists()) + value.update(phase="running", exported_successful_episodes=1) + with self.assertRaises(TimeoutError): + session.wait_for_export(timeout=0.001) + + def test_invalid_tool_and_gripper_inputs_never_send_motion(self): + session = FrankaTaskSession(observation_dir="/tmp/observer", output="/tmp/task") + session.controller = Mock() + for point, quat in ( + ([0, 0], [0, 0, 0, 1]), + ([0, 0, 1], [0, 0, 0, 0]), + ([np.inf, 0, 1], [0, 0, 0, 1]), + ): + with self.assertRaises(ValueError): + session.move_tool(point, quat) + for closed in (1, "false", None): + with self.assertRaises(TypeError): + session.set_gripper(closed=closed) + session.controller.move_world.assert_not_called() + + def test_improper_rotation_rejected_before_browser_command(self): + bridge = XRControllerBridge.__new__(XRControllerBridge) + bridge.client = Mock() + with self.assertRaises(ValueError): + bridge.target_base([0.4, 0, 0.2], np.diag([1, 1, -1])) + bridge.client.command.assert_not_called() + + def test_cleanup_failure_overrides_pass_and_still_requests_observer_stop(self): + with TemporaryDirectory() as directory: + root = Path(directory) + output = root / "task" + output.mkdir() + (root / "sim-state.json").write_text(json.dumps(observation(root))) + (output / "result.json").write_text('{"status":"PASS"}') + session = FrankaTaskSession(observation_dir=root, output=output) + session.run_id = "one" + session.client.command = Mock(side_effect=RuntimeError("disconnect")) + with self.assertRaisesRegex(RuntimeError, "disconnect"): + session.__exit__(None, None, None) + self.assertTrue((root / "stop").exists()) + self.assertEqual( + json.loads((output / "result.json").read_text())["status"], "FAIL" + ) + + def test_export_alone_cannot_mask_worker_failure_or_missing_close(self): + with TemporaryDirectory() as directory: + root = Path(directory) + value = observation(root) + value["exported_successful_episodes"] = 1 + for closed, code, interrupted, valid in ( + (False, 0, False, False), + (True, 1, False, False), + (True, 0, True, False), + (True, 0, False, True), + ): + value["native_environment_closed"] = closed + (root / "sim-state.json").write_text(json.dumps(value)) + result = completion_snapshot(root, "one", code, interrupted) + self.assertEqual(result["completion_error"] is None, valid) + + def test_second_driver_cannot_send_input_or_stop_first_driver(self): + with TemporaryDirectory() as directory: + root = Path(directory) + (root / "driver-owner.json").write_text('{"run_id":"one","pid":1}') + session = FrankaTaskSession(observation_dir=root, output=root / "second") + session.client.state = Mock(return_value=observation(root)) + session.client.command = Mock() + with self.assertRaises(FileExistsError): + session.__enter__() + session.client.command.assert_not_called() + self.assertFalse((root / "stop").exists()) + + def test_unbounded_timeouts_rejected_before_io(self): + session = FrankaTaskSession(observation_dir="/tmp/observer", output="/tmp/task") + for timeout in (-1, 0, float("nan"), float("inf"), 301): + with self.subTest(timeout=timeout): + with self.assertRaises(ValueError): + session.wait_for_export(timeout) + with self.assertRaises(ValueError): + session.client.command("inspect", timeout=timeout) + + def test_stack_module_is_importable_without_starting_task(self): + import isaaccapture_examples.synthetic_xr.franka.stack_logic as stack + + self.assertTrue(callable(stack.main)) + + +if __name__ == "__main__": + unittest.main() From 38cd3e4109a4950dabbe145cae3d2258b5e91fc4 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Sun, 18 Oct 2026 10:28:18 +0000 Subject: [PATCH 07/11] build: pin the native-tested WebXR client dependency graph Signed-off-by: Sameer Tangade --- .gitignore | 2 + deps/cloudxr/webxr_client/package-lock.json | 14414 ++++++++++++++++++ examples/synthetic_xr/README.md | 2 +- 3 files changed, 14417 insertions(+), 1 deletion(-) create mode 100644 deps/cloudxr/webxr_client/package-lock.json diff --git a/.gitignore b/.gitignore index 1355488147..df9bd377aa 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ # Node.js node_modules/ package-lock.json +# Keep the tested Synthetic XR client dependency graph reproducible. +!deps/cloudxr/webxr_client/package-lock.json # Build directories build/ diff --git a/deps/cloudxr/webxr_client/package-lock.json b/deps/cloudxr/webxr_client/package-lock.json new file mode 100644 index 0000000000..cf0d7eb63d --- /dev/null +++ b/deps/cloudxr/webxr_client/package-lock.json @@ -0,0 +1,14414 @@ +{ + "name": "cloudxr-isaac-lab-teleop", + "version": "6.3.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cloudxr-isaac-lab-teleop", + "version": "6.3.0", + "license": "Apache-2.0", + "dependencies": { + "@nvidia/cloudxr": "file:../nvidia-cloudxr-6.3.0.tgz", + "@preact/signals-react": "^3.10.0", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.6.0", + "@react-three/handle": "^6.6.29", + "@react-three/uikit": "^1.0.64", + "@react-three/uikit-default": "^1.0.64", + "@react-three/xr": "^6.6.29", + "react": "~19.2.5", + "react-dom": "~19.2.5", + "three": "^0.184.0", + "uuid": "^14.0.0" + }, + "devDependencies": { + "@playwright/test": "^1.63.0", + "@types/jest": "^30", + "@types/node": "^22", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@types/three": "^0.184.0", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "^8.32.1", + "@typescript-eslint/parser": "^8.32.1", + "@webxr-input-profiles/assets": "1.0.20", + "copy-webpack-plugin": "^13.0.0", + "cross-env": "^10.1.0", + "css-loader": "^6.8.1", + "eslint": "9.27.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-simple-import-sort": "^12.1.1", + "html-webpack-plugin": "^5.6.7", + "iwer": "^2.3.0", + "jest": "^30", + "jest-environment-jsdom": "^30.4.1", + "prettier": "3.5.3", + "rimraf": "^5.0.5", + "style-loader": "^3.3.3", + "ts-jest": "^29", + "ts-loader": "^9.5.7", + "typescript": "^5.8.2", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.3", + "ws": "^8.22.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.10.tgz", + "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.16.0.tgz", + "integrity": "sha512-FWa0sPlqYGJgpTs6OxBRcL/AW4JT0OIO+W1ajerluoTVh8CV0B7XM1qsNCRgnkSCkRCgOICKHRQlY17jrsm0+Q==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "license": "Apache-2.0" + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", + "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "deprecated": "v0.2.x is no longer supported. Unless you are still using FontAwesome 5, please update to v3.1.1 or greater.", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@iwer/devui": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@iwer/devui/-/devui-1.1.2.tgz", + "integrity": "sha512-ggF1lXSX14BTYP0QzB4xaurySr2PC+3+rtK/dpCR++giWquzFv2mBw3LW/PaCtdl5mqkZMrQ2GSwfUNg9ZoO+w==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "6.6.0", + "@fortawesome/free-solid-svg-icons": "6.6.0", + "@fortawesome/react-fontawesome": "0.2.2", + "@pmndrs/handle": "^6.6.17", + "@pmndrs/pointer-events": "^6.6.17", + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "styled-components": "^6.1.13", + "three": "^0.165.0" + }, + "peerDependencies": { + "iwer": "^2.0.1" + } + }, + "node_modules/@iwer/devui/node_modules/styled-components": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.6.0.tgz", + "integrity": "sha512-drKKsqvDBxVRn4ySU67iQ5nQBuo/1xyYeBfdhDde112PkqrS3+95KPnpDs5hFRcmrW+q6+gbyywYYYR14oWFtw==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "@types/stylis": "4.2.7", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "stylis": "4.3.6" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "css-to-react-native": ">= 3.2.0", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-native": ">= 0.68.0" + }, + "peerDependenciesMeta": { + "css-to-react-native": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@iwer/devui/node_modules/three": { + "version": "0.165.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz", + "integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==", + "license": "MIT" + }, + "node_modules/@iwer/sem": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@iwer/sem/-/sem-0.2.5.tgz", + "integrity": "sha512-vMCfpu/7Qqc+hkBiGD9pxjeObgrhXOrL0KX94CA3yzJaU0dq0y49HXZT6fC+6X/jOmjaM3hjyE1m2h7ZmLzzyA==", + "license": "MIT", + "dependencies": { + "three": "^0.165.0", + "ts-proto": "^2.6.0" + }, + "peerDependencies": { + "iwer": "^2.0.0" + } + }, + "node_modules/@iwer/sem/node_modules/three": { + "version": "0.165.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz", + "integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==", + "license": "MIT" + }, + "node_modules/@jest/console": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.5.2.tgz", + "integrity": "sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.5.2.tgz", + "integrity": "sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.5.2", + "@jest/pattern": "30.5.0", + "@jest/reporters": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.5.1", + "jest-config": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-resolve-dependencies": "30.5.2", + "jest-runner": "30.5.2", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "jest-watcher": "30.5.2", + "pretty-format": "30.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz", + "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz", + "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-mock": "30.5.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.5.2.tgz", + "integrity": "sha512-8/P+8hKv7L4mhFoB6gpYLj/fuzXk8PWrde7TDHh8tJz5pE1ktlMbg0y6hDY/V8MfAnispFlPl/vPifowLy3JGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/jsdom": "*", + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/@jest/expect": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.5.2", + "jest-snapshot": "30.5.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.2.tgz", + "integrity": "sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.5.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz", + "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.5.1", + "@sinonjs/fake-timers": "^15.4.0", + "@types/node": "*", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.5.0.tgz", + "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.5.2.tgz", + "integrity": "sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/types": "30.5.1", + "jest-mock": "30.5.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.5.0.tgz", + "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.5.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/react-is-19": { + "name": "react-is", + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.1.tgz", + "integrity": "sha512-RRieF6IyxQBkcK6pP8xOHprLTPW6QjRkbGdImtqNme+/hYVwCZkLQLdlZGOhUqXwkS9IEynaGvPJtv6XB/cz0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/reporters": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.2.tgz", + "integrity": "sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.5.1.tgz", + "integrity": "sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.5.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.2.tgz", + "integrity": "sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "callsites": "^3.1.0", + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.2.tgz", + "integrity": "sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.5.2", + "@jest/types": "30.5.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.2.tgz", + "integrity": "sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.5.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz", + "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", + "babel-plugin-istanbul": "^8.0.0", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.80.0.tgz", + "integrity": "sha512-qMKWshnyjbyhm+NbzqGE3w5y1mckFFEMj0aDPU2/JBNh1BzIjVy9esq9lBmRnNVmhMEkxwDzLZ8Gqh5G1P5Dag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.80.0.tgz", + "integrity": "sha512-ZBEKl7J6dbkRCfmrFV48Re211A/FHqniTUhsWCsaRteNSOqMgkX3qF1UreVgwB9oSPm81JLAwhIi6Lb/NH/PoA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.80.0.tgz", + "integrity": "sha512-yiBlUfkMMRFFGy8CD/h2zd5rPnrM6bjlF+nx2LRAxNV6hOSiuObQIUEE7m7y2VTMisjxCRtzINH94bFGySWL7g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "@jsonjoy.com/fs-print": "4.80.0", + "@jsonjoy.com/fs-snapshot": "4.80.0", + "glob-to-regex.js": "^1.3.1", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.80.0.tgz", + "integrity": "sha512-OIOIhqaWiwUySFMny4epIAoviXsyMaQQ63PTc3zH+5HEOvarl5hpUmZIhp2m5+OfyKAh5ylxXnQIgew1OgJDVw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.80.0.tgz", + "integrity": "sha512-ljeaR5xwtX1EyRmB0wvXANUMQZ3mjKcm7z8s2N8D8SLQ04CcAGtUhHFRZQXFb9mhgFyEVMydQGQtSOFS7pcrFQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.80.0.tgz", + "integrity": "sha512-xwHeVVi3f+Khg/Fmb8i5qoYVmQ08QznE5TWhQokUmIZjz4KBPzU+zt/+bahBI6G1ghtqajt27lWuAciIxApuzA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "glob-to-regex.js": "^1.3.1" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.80.0.tgz", + "integrity": "sha512-1ocZLJw/m/xlq0moaIHO7HV9nHRVBUvdc2zMzhsJGkFOrqfwWvexfCv5cYCPPLRhP+9p3yunoi4Ep5YhRORC9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.80.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.80.0.tgz", + "integrity": "sha512-3AV23Q1H228KTtJKTUE0nNq8FCtxSvtENOLrEat80wkAJCKHuqQEv8yjhpS8Bei2OEFJAnluGXp9Ri2SWBlROw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mediapipe/tasks-vision": { + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", + "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==", + "license": "Apache-2.0" + }, + "node_modules/@monogrid/gainmap-js": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.1.tgz", + "integrity": "sha512-/EIVp+w3RbUel914edq4NcHnmAiG6E6rDcsg2ZXfIl9aVeeKqP+r9Jw9Ey0dER90uuhjwN7xpdSuPAMTPP19fw==", + "license": "MIT", + "dependencies": { + "promise-worker-transferable": "^1.0.4" + }, + "peerDependencies": { + "three": ">= 0.159.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.5.tgz", + "integrity": "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nvidia/cloudxr": { + "version": "6.3.0", + "resolved": "file:../nvidia-cloudxr-6.3.0.tgz", + "integrity": "sha512-RtoU0sAt302ZVj5bAgzi/QbJDhps8PWbZXV9Jj2Tz740ASVjqrAIZdqUSI8oYP8CnsIMVUgs+vnAKSi1qdKLgg==", + "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "gl-matrix": "^3.4.4", + "long": "^5.3.2" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.11.0.tgz", + "integrity": "sha512-SlAiDoT/Cd8qaNmbTt78gqyaEvu2fQd6ML6KWJkdjXGrgDKnHHrusVkfQUelkfinL2p665IIADb0JBixtT8W1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "@peculiar/asn1-x509-attr": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.11.0.tgz", + "integrity": "sha512-M/ED0yJ05qMK+UADPKnIa8vhAeewOJALSxTAZB7RQ/batRHhIJZkwznCcWAbFO5saHJ1yEQ+m4lncEcG9RnQSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.11.0.tgz", + "integrity": "sha512-jj1alGEKev7/un+cI+exyVK9m31lmP7cCBMCZuCb0wkFQH2zmKmJzNiG4wqXFNzlcP8timHLnzf3lV3OxBpb/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.11.0.tgz", + "integrity": "sha512-ykSAWRu8KkmzU8LRpk9ZQC3zuLmBEtoSMjIv9XHVamD5W7amqreV5wttKqnvtjWOzzSJQxA/HVT8/tHwpEEGSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.11.0", + "@peculiar/asn1-pkcs8": "^2.11.0", + "@peculiar/asn1-rsa": "^2.11.0", + "@peculiar/asn1-schema": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.11.0.tgz", + "integrity": "sha512-lragjkjQGnQ5luxC+sxoAfAO/jBiQ7hOEGlDKJUk7MC5zY3AzjB2DU0GyPUH1Sswdv10t7DurY0zylOYMLb88w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.11.0.tgz", + "integrity": "sha512-gDdFjpqV0weJjgNUvueUVf4D8uVbpq+VbG0TsxLAY2QGljAkJyL9qwQ7avol9j0jaYSJ7Ey7v7RZp0M3GTcQSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.11.0", + "@peculiar/asn1-pfx": "^2.11.0", + "@peculiar/asn1-pkcs8": "^2.11.0", + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "@peculiar/asn1-x509-attr": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.11.0.tgz", + "integrity": "sha512-S2sBJmbfI09Kzdt9cEhE6pDNjHpVTkURs+811+PxS8AvsTLxC4woMNRhxHYaIbClkkNfAVt0tZxJdINVfUmzuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.11.0.tgz", + "integrity": "sha512-8xh4avo53/vXO6UEv9yaoJ83hY581geaLTygf61NPIXP5AeIZCPjJhpTaYU23IZkG+NpLN/C9FYzWp7hphIT9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.11.0.tgz", + "integrity": "sha512-J1sHAwGNlk+8J+FeR1eLVc6Gq0T9f9ITc3HWyPYgJraoW6U8AJuQiWVJ3SCEpltfTdpOY7fHdHT5oKAsbK/jww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.11.0.tgz", + "integrity": "sha512-Fgvnd9REmLxEj/4cbDP54dNUNUXkzUksXLqEHqs1mHBuSRjSTCUSPM/WQIZYSvrdG7ND9CX1AQtPoIBekhgQSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.11.0", + "@peculiar/asn1-x509": "^2.11.0", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@playwright/test": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz", + "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@pmndrs/handle": { + "version": "6.6.32", + "resolved": "https://registry.npmjs.org/@pmndrs/handle/-/handle-6.6.32.tgz", + "integrity": "sha512-iS/mjwfB2RZrld6dCIKR6wlyYYt3vEG3D5e4GxLvuuS4nkRvJuaJC0HYnSupS8dv4N4D3Smdojp/mN5r55Fztw==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/pointer-events": "~6.6.32", + "zustand": "^4.5.2" + } + }, + "node_modules/@pmndrs/handle/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@pmndrs/msdfonts": { + "version": "1.0.77", + "resolved": "https://registry.npmjs.org/@pmndrs/msdfonts/-/msdfonts-1.0.77.tgz", + "integrity": "sha512-Lf7Uq12WMyywNwql7roBRyggxdH3XXAsEgae+zbXv4iVMcJa0cb+iLyqb6zpK+JSPHF0DMghmygJYcgf8/JYbw==", + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/@pmndrs/pointer-events": { + "version": "6.6.32", + "resolved": "https://registry.npmjs.org/@pmndrs/pointer-events/-/pointer-events-6.6.32.tgz", + "integrity": "sha512-ziow8WQach6AF3fB8XGfmxmjLH6iUKPDJsw5RzxU7oxhfpn4uQZKzq50U1LFywroMZ45JhCqxE+p7IM8MHRsFg==", + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/@pmndrs/uikit": { + "version": "1.0.77", + "resolved": "https://registry.npmjs.org/@pmndrs/uikit/-/uikit-1.0.77.tgz", + "integrity": "sha512-HSuZbItVhl+xOtBxR5pJ/DqeHzlfVFNbGj/VFeDWU2We6vuYVs3/L6Lcmbxi/+qNXMn6YS5LjXBgtFwvjAXiWg==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/msdfonts": "^1.0.77", + "@pmndrs/uikit-pub-sub": "^1.0.77", + "@preact/signals-core": "^1.5.1", + "@zappar/msdf-generator": "^1.2.4", + "yoga-layout": "^3.2.1", + "zod": "^4.4.3" + }, + "peerDependencies": { + "three": ">=0.162" + } + }, + "node_modules/@pmndrs/uikit-default": { + "version": "1.0.77", + "resolved": "https://registry.npmjs.org/@pmndrs/uikit-default/-/uikit-default-1.0.77.tgz", + "integrity": "sha512-BCb4UJn7RXUGO6LLZBj/zIo3KnNzsXEoF8y01Y/3CeEiVQrqCACV70WpqOyCTh64hgT/1OFfh09828QVIEAhYQ==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/uikit": "^1.0.77", + "@pmndrs/uikit-lucide": "^1.0.77", + "zod": "^4.4.3" + } + }, + "node_modules/@pmndrs/uikit-lucide": { + "version": "1.0.77", + "resolved": "https://registry.npmjs.org/@pmndrs/uikit-lucide/-/uikit-lucide-1.0.77.tgz", + "integrity": "sha512-6+eXzM1YaQNm7QqfOMGWjMnyBaQUw6h2iNTqlGipVd8PeFwOxTJfNu8+AMBKyAtH6rNjDnf3VeLy7ZQyPhSTSg==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/uikit": "^1.0.77" + } + }, + "node_modules/@pmndrs/uikit-pub-sub": { + "version": "1.0.77", + "resolved": "https://registry.npmjs.org/@pmndrs/uikit-pub-sub/-/uikit-pub-sub-1.0.77.tgz", + "integrity": "sha512-nfkXQF///Tz6/jIfCtJCxuHMHfLxWj+72fDTIeAlK8MteAelTPtHUH7JKvyoLay4HcGSWuCWJx8Hr5ax6dKX0g==", + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.8.0" + } + }, + "node_modules/@pmndrs/xr": { + "version": "6.6.32", + "resolved": "https://registry.npmjs.org/@pmndrs/xr/-/xr-6.6.32.tgz", + "integrity": "sha512-d5fLwVlRpoZ8tcQNLw7jbiOA1jsboKdjH4+6gYCoT+PVRtTwsoJOwYaeI48i3rz17mLLmZ7pEABC47ZMicO10A==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@iwer/devui": "^1.1.1", + "@iwer/sem": "~0.2.5", + "@pmndrs/pointer-events": "~6.6.32", + "iwer": "^2.1.0", + "meshline": "^3.3.1", + "zustand": "^4.5.2" + }, + "peerDependencies": { + "three": "*" + } + }, + "node_modules/@pmndrs/xr/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@preact/signals-core": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.4.tgz", + "integrity": "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@preact/signals-react": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@preact/signals-react/-/signals-react-3.12.0.tgz", + "integrity": "sha512-o3zBSekC/RPcsHOYTLagjw6JX0ih5eMMFZE0AICEeuk7p3MrYOyEXxM7bmT1Uqi0jPy2pbuDQZTFXYe55IAFVg==", + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.14.4", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + }, + "peerDependencies": { + "react": "^16.14.0 || 17.x || 18.x || 19.x" + } + }, + "node_modules/@react-three/drei": { + "version": "10.7.9", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.9.tgz", + "integrity": "sha512-wP4Y6o58KFi+hT1zCfjJarRtPCT6WW/lo53WMiA/aCORuUvBzW916XsCtadnIB1KB+3IQNcDU5JeVh/gYb8MYw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mediapipe/tasks-vision": "0.10.17", + "@monogrid/gainmap-js": "^3.0.6", + "@use-gesture/react": "^10.3.1", + "camera-controls": "^3.1.0", + "cross-env": "^7.0.3", + "detect-gpu": "^5.0.56", + "glsl-noise": "^0.0.0", + "hls.js": "^1.5.17", + "maath": "^0.10.8", + "meshline": "^3.3.1", + "stats-gl": "^2.2.8", + "stats.js": "^0.17.0", + "suspend-react": "^0.1.3", + "three-mesh-bvh": "^0.8.3", + "three-stdlib": "^2.35.6", + "troika-three-text": "^0.52.4", + "tunnel-rat": "^0.1.2", + "use-sync-external-store": "^1.4.0", + "utility-types": "^3.11.0", + "zustand": "^5.0.1" + }, + "peerDependencies": { + "@react-three/fiber": "^9.0.0", + "react": "^19", + "react-dom": "^19", + "three": ">=0.159" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@react-three/drei/node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@react-three/fiber": { + "version": "9.8.2", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.8.2.tgz", + "integrity": "sha512-6s0ejw0bT1myQ/aLLugcil8/zBmNnnHw/AISWF1yfDLOxfX3yYaTS+0IkpJITEnm/SSMLtxje6CJDt0i5Wu1Sw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/webxr": "*", + "base64-js": "^1.5.1", + "buffer": "^6.0.3", + "its-fine": "^2.1.1", + "react-use-measure": "^2.1.7", + "scheduler": "^0.28.0", + "suspend-react": "^0.1.3", + "use-sync-external-store": "^1.4.0", + "zustand": "^5.0.3" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-file-system": ">=11.0", + "expo-gl": ">=11.0", + "react": ">=19 <19.4", + "react-dom": ">=19 <19.4", + "react-native": ">=0.78", + "three": ">=0.156" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-file-system": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/handle": { + "version": "6.6.32", + "resolved": "https://registry.npmjs.org/@react-three/handle/-/handle-6.6.32.tgz", + "integrity": "sha512-1C4DH8cnHU24xGto1L4t0Gi5KMk4yJVK+R2ojqhrz97B5QHRGsKn5pgZBhMlXotrvSLESM875QjZNP9cgBm+Wg==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/handle": "~6.6.32", + "@pmndrs/pointer-events": "~6.6.32", + "zustand": "^4.5.2" + }, + "peerDependencies": { + "@react-three/fiber": ">=8", + "react": ">=18", + "three": "*" + } + }, + "node_modules/@react-three/handle/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@react-three/uikit": { + "version": "1.0.77", + "resolved": "https://registry.npmjs.org/@react-three/uikit/-/uikit-1.0.77.tgz", + "integrity": "sha512-ZHtepWERLseCFRmGXr6/EMpq9RHKIkIUheNyyLRTna7WS+sDbe8SuHDsiEHb3Nv73evtrpjC/1dGEoXcdPEfZw==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/pointer-events": "^6.6.29", + "@pmndrs/uikit": "^1.0.77", + "@preact/signals-core": "^1.5.1", + "suspend-react": "^0.1.3", + "zustand": "^5.0.6" + }, + "peerDependencies": { + "@react-three/fiber": ">=8", + "react": ">=18" + } + }, + "node_modules/@react-three/uikit-default": { + "version": "1.0.77", + "resolved": "https://registry.npmjs.org/@react-three/uikit-default/-/uikit-default-1.0.77.tgz", + "integrity": "sha512-hHOBJaQ7Np/ANxNWIDk0PxCH5BtgbPCJ989Pbf8CgNM+4rmQOAACg5abrVPHlKJpEyBAA5wwYdn4e/6ki8bbLw==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/uikit-default": "^1.0.77", + "@react-three/uikit": "^1.0.77" + } + }, + "node_modules/@react-three/xr": { + "version": "6.6.32", + "resolved": "https://registry.npmjs.org/@react-three/xr/-/xr-6.6.32.tgz", + "integrity": "sha512-mfD0cuYQps01wfqpu211rPYClG78/bnZtT+GeClclhY+88l3lBww2Timh9r+1pnZobwN1obSgMG2u0g6zs1eow==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@pmndrs/pointer-events": "~6.6.32", + "@pmndrs/xr": "~6.6.32", + "suspend-react": "^0.1.3", + "tunnel-rat": "^0.1.2", + "zustand": "^4.5.2" + }, + "peerDependencies": { + "@react-three/fiber": ">=8", + "react": ">=18", + "react-dom": ">=18", + "three": "*" + } + }, + "node_modules/@react-three/xr/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/draco3d": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", + "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.9", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz", + "integrity": "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@types/react-reconciler": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "license": "MIT" + }, + "node_modules/@types/stylis": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz", + "integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.184.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.184.1.tgz", + "integrity": "sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA==", + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "fflate": "~0.8.2", + "meshoptimizer": "~1.1.1" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.2.tgz", + "integrity": "sha512-K72/pvT7qpm8q3k8G5u93NCSN1RcLxX5h7Gw0bQpbMXviBHeY6Nx1WEwmk9wP+eKTSQ8ecxvcgt/ZX/rB938xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.71.2", + "@typescript-eslint/type-utils": "8.71.2", + "@typescript-eslint/utils": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.71.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.2.tgz", + "integrity": "sha512-aTtmZjp/x3gsGHkDs04/1I8kNogBJkpQaeGaN0PfiG43NzDEmW2n+OP5im9wfsvyX0yP1UZWBrAGtDBfH9jEVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.71.2", + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.2.tgz", + "integrity": "sha512-Pd68GsLVh5ZXlcEccFPdhSOvtHZ/2WU0XsA8zmXOS3F158lVlcTLhfQ6qvtL1RzbMie0Kp8xtbVwAg4nejKPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.71.2", + "@typescript-eslint/types": "^8.71.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.2.tgz", + "integrity": "sha512-YF4wgg5iUX5Obywoac27B56FhozMNFhdJSbh11H5GBAwmBtfyRhmgTQqW9tRNNmHDvsZ2CX6UGzoz+YfYn2Leg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.2.tgz", + "integrity": "sha512-jz49TAWokm/7yWD9/o837nYax9DyALxIPOImdImvVzo6canBCZYzin8zJyqNR5k1j9K1DE0bVvr0XYB+99uAdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.2.tgz", + "integrity": "sha512-cC8UaalCViJkb3rH/xLIvidEwoFRtMZMUqDjjJMGEKaTFwYRHYBcoczlEGHuvkHEO8svRDrgZ0yQgOArFmwang==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2", + "@typescript-eslint/utils": "8.71.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.2.tgz", + "integrity": "sha512-y5Ukl3oVp7e/VGxqIoEXoeb/Roj71PAJ4WG/N/RGj+eeDvKRLM15kGr9MijLbN0i03S52zxu31Z98E5gWChJKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.2.tgz", + "integrity": "sha512-DgNOEsl4pdYzwSL+N/Cl33tX1Hlw3FvId73LnKXfq7jnrF2viOEn7X+P7RBkkSWCVm92gnWd28GIC5vJuLREeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.71.2", + "@typescript-eslint/tsconfig-utils": "8.71.2", + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/visitor-keys": "8.71.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.2.tgz", + "integrity": "sha512-RyKa1G1M9rKHDhrk+HJtiF1MvLD5Ux2JW3TSkzw+02pRALdpJb+QzG1Y5em+LvCgzGwgg8H7mBRFKljNz7ImPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.2", + "@typescript-eslint/types": "8.71.2", + "@typescript-eslint/typescript-estree": "8.71.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.71.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.2.tgz", + "integrity": "sha512-zozlN0UA32bSG2zkKAgQyRYSbEISVoyU6vvkKYo2aSny74D8Jh4e+POkJ6Lk0dlezZi5x7TtD+88Xb06IB8nhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@webxr-input-profiles/assets": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@webxr-input-profiles/assets/-/assets-1.0.20.tgz", + "integrity": "sha512-V/8NB4Ywgm//TBVMgo0TgJ9DtxsBoo0iiaF8AdfiP0F0cgbwkuQycEZAIOsplyd/iZ8L3mrFYNJBC6rzQb4tJA==", + "dev": true + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@zappar/msdf-generator": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@zappar/msdf-generator/-/msdf-generator-1.2.4.tgz", + "integrity": "sha512-6S/MCk0Ky0ipewZJw4xFEzH/2aYfWmPXEkTdBtNyDDfkbicrNwgJgtxZ4SnTDyNe9XHMqDA4sL9srRsgDLRMqA==", + "license": "MIT", + "dependencies": { + "comlink": "^4.4.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.2.0.tgz", + "integrity": "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "es-shim-unscopables": "^1.1.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asn1js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.1.0.tgz", + "integrity": "sha512-HWYi1THkOqguCiO+vwj09ZkQFD2kKEsejYHAYCtX0D80WVdoDnvRc9IukXX/jeb0T9NRWJnRtqwdbHltnFjwXA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.2.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.2.tgz", + "integrity": "sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.5.2", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^8.0.0", + "babel-preset-jest": "30.5.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz", + "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^7.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.5.0.tgz", + "integrity": "sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.5.0.tgz", + "integrity": "sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.5.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1 || ^8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.29.tgz", + "integrity": "sha512-YeesxhUmR4h5y5V8KF8iBCqJb8YFrBJ++XBTeGcWIaQOYXGN19JM0FoiOjAhwuDoln8owMgXcWBmgHtYSkky2w==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.1.0.tgz", + "integrity": "sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.8", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", + "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.16.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.4.4.tgz", + "integrity": "sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.2.tgz", + "integrity": "sha512-V2o8YS2EhrKMO9AumnkK0ynynfFlsajpYupwq7S46H5mhcanwxDkNTw3K67wPIcKTH1ffkA4vhMOtAc7gRmPZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camera-controls": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.2.tgz", + "integrity": "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==", + "license": "MIT", + "engines": { + "node": ">=22.0.0", + "npm": ">=10.5.1" + }, + "peerDependencies": { + "three": ">=0.126.1" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001816", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001816.tgz", + "integrity": "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/case-anything": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz", + "integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.3.0.tgz", + "integrity": "sha512-lmNyBzi6iYiqyrExTctfmjd8tInx+dHu9PBomTfH5RXS53c27JMFJgzNv5TyAnm3PgGf+MLy0+Ws41/HlVxdKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz", + "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "destroy": "1.2.0", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", + "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-gpu": { + "version": "5.0.70", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz", + "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==", + "license": "MIT", + "dependencies": { + "webgl-constants": "^1.1.1" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dprint-node": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz", + "integrity": "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==", + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3" + } + }, + "node_modules/dprint-node/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/draco3d": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.9.tgz", + "integrity": "sha512-MqC0eAfW5gPATGY3GABIyFWpHwdDGAyewTdek5R/e+mhI9ZDUMKb2mTDkTMUW0+Vm7an+ThEMUop3+svS72Uug==", + "license": "Apache-2.0" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.459", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.459.tgz", + "integrity": "sha512-4SC9HHSFjO/liAhifpdOL5hG9b4BP7/5G3y3UPZVO8trlAXr1CaUEEJL0gWK3kOcYcZrOA5rkwG45Gra/lTIgg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", + "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/express": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz", + "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.13", + "proxy-addr": "~2.0.7", + "qs": "~6.16.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.4.tgz", + "integrity": "sha512-tGQtaoYisPDriJBN0mT5yZSyNETnJGA1gTYxaVRAE5KUO3LI9kPUc9WIu8JXcxaIci+faIKlzXqXyPgrmubtBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bser": "2.1.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.3.1.tgz", + "integrity": "sha512-zhWhMRsgnOym+1txhUmYJGD6eUsbUdrZv55gCYKuJX8t2p3+M8My23PD09bEIsreGUs5pnSa/idmcfp2/IlqGg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glsl-noise": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", + "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==", + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hls.js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.7.3.tgz", + "integrity": "sha512-MsPlx6yVW4Qv4C7mEVou4/gk/5cN2dVxOTnrNjPSmyH2f0Ln+/UkG10Ax1PE6OvrmTVPKgQYozTtBaPOuCzq7A==", + "license": "Apache-2.0" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.8", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.8.tgz", + "integrity": "sha512-MZmKQcTnhEh1SPSyMiEytIeDZDUoBZVorNHivQGXMASHf/BSGGOrKa2xQ5bGx3TCe1n109ecCt+cpww7wwWhKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x || 2.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz", + "integrity": "sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.12.tgz", + "integrity": "sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz", + "integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.2.tgz", + "integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/its-fine": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.1.1.tgz", + "integrity": "sha512-C5ZoVkeqSt0Aje0tzESO+iT/EQpNFNL39GkP7mCACtoZUFD6GcyerFCr6kp0ZtiII4W9HoYkY48Soy5xE/uHKw==", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.9" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/iwer": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/iwer/-/iwer-2.6.0.tgz", + "integrity": "sha512-DNihqCixizrNjPKScTt0aOy9+YP1pn0Jxj1V6tBOV0ceRm6YrEMLo8QhEwYspPYJ9QZjDJShDMdZAaPXDPaSAw==", + "license": "MIT", + "dependencies": { + "gl-matrix": "^3.4.4", + "webxr-layers-polyfill": "^1.1.0" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.5.2.tgz", + "integrity": "sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.5.2", + "@jest/types": "30.5.1", + "import-local": "^3.2.0", + "jest-cli": "30.5.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.5.1.tgz", + "integrity": "sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.5.1", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.2.tgz", + "integrity": "sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", + "p-limit": "^3.1.0", + "pretty-format": "30.5.1", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.2.tgz", + "integrity": "sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.5.2.tgz", + "integrity": "sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.5.0", + "@jest/pattern": "30.5.0", + "@jest/test-sequencer": "30.5.2", + "@jest/types": "30.5.1", + "babel-jest": "30.5.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "jest-circus": "30.5.2", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-runner": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "parse-json": "^5.2.0", + "pretty-format": "30.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.2.tgz", + "integrity": "sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.5.0", + "@jest/get-type": "30.5.0", + "chalk": "^4.1.2", + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.5.0.tgz", + "integrity": "sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.5.2.tgz", + "integrity": "sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", + "chalk": "^4.1.2", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.5.2.tgz", + "integrity": "sha512-6NOZjEP+WNi4WXjbxb/1ROnfaMxn6Dhqu6uSC22o0H28z+uppejs/jZ9KCVoLepqqoCjcNZujtzQqC9jSV15jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/environment-jsdom-abstract": "30.5.2", + "@types/jsdom": "^21.1.7", + "jsdom": "^26.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.2.tgz", + "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-mock": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz", + "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.5.1", + "@parcel/watcher": "^2.6.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "fdir": "^6.5.0", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.5.1.tgz", + "integrity": "sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.5.0", + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.2.tgz", + "integrity": "sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.5.0", + "chalk": "^4.1.2", + "jest-diff": "30.5.2", + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz", + "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.5.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.5.1", + "picomatch": "^4.0.3", + "pretty-format": "30.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz", + "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-util": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-regex-util": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz", + "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.5.1.tgz", + "integrity": "sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "slash": "^3.0.0", + "unrs-resolver": "^1.12.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.2.tgz", + "integrity": "sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.5.0", + "jest-snapshot": "30.5.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.2.tgz", + "integrity": "sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.5.2", + "@jest/environment": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-leak-detector": "30.5.1", + "jest-message-util": "30.5.1", + "jest-resolve": "30.5.1", + "jest-runtime": "30.5.2", + "jest-util": "30.5.1", + "jest-watcher": "30.5.2", + "jest-worker": "30.5.1", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.2.tgz", + "integrity": "sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/globals": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.2.0", + "collect-v8-coverage": "^1.0.2", + "es-module-lexer": "^2.1.0", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.2.tgz", + "integrity": "sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "@jest/snapshot-utils": "30.5.1", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.5.2", + "graceful-fs": "^4.2.11", + "jest-diff": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "pretty-format": "30.5.1", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz", + "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.5.1.tgz", + "integrity": "sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.5.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.2.tgz", + "integrity": "sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.5.1", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.5.1.tgz", + "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.5.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.2.tgz", + "integrity": "sha512-NHJEb30MGBCkmZXLZwz25t37QR4aY03ZzPYUJXOJaOa92uWwExI9SBQUuIznBYc1xkD7cJNGX7oc3Nl3sq2kQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.10.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/maath": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz", + "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.134.0", + "three": ">=0.134.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.80.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.80.0.tgz", + "integrity": "sha512-N70W2isi44XdGn7uPo9NJ4zRukcdkwSFHlnYx42SWOH/+hzqNc8P7+tVoA2up4z7faHvNi1RSNCFjxoYQ71VPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/fs-core": "4.80.0", + "@jsonjoy.com/fs-fsa": "4.80.0", + "@jsonjoy.com/fs-node": "4.80.0", + "@jsonjoy.com/fs-node-builtins": "4.80.0", + "@jsonjoy.com/fs-node-to-fsa": "4.80.0", + "@jsonjoy.com/fs-node-utils": "4.80.0", + "@jsonjoy.com/fs-print": "4.80.0", + "@jsonjoy.com/fs-snapshot": "4.80.0", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.3.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/meshline": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz", + "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/meshoptimizer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz", + "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.13.0.tgz", + "integrity": "sha512-OO1NF2zHj6sf4CNv2AsUWYcS8WEwkVKQDXxxKzlOxx1Xl+WlqYtVC+oNlGNwGkLAOnpj277BSHypWwRtd9w4JQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.3", + "terser": "^5.51.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@napi-rs/image": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "imagemin": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "sharp": { + "optional": true + }, + "svgo": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.59", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.59.tgz", + "integrity": "sha512-5qr2okC4ItAI0hG5SaV0HoYMpnM/Y6+Oqr7AmPYy/2uEoGvS+GbSKLeRxSS1uAdu59yz0ouzcuXievSgg///WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.28", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.28.tgz", + "integrity": "sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkijs": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz", + "integrity": "sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "website" + ], + "dependencies": { + "@noble/hashes": "1.8.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/playwright": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz", + "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", + "license": "ISC" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/promise-worker-transferable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz", + "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==", + "license": "Apache-2.0", + "dependencies": { + "is-promise": "^2.1.0", + "lie": "^3.0.2" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/qs": { + "version": "6.16.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz", + "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "19.2.9", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.9.tgz", + "integrity": "sha512-RuLdAokXv3sRmZa2/N8QH0KmTBIgDKC+buRUikGqow31+mGSGidLCX2o4XFq8OV+xBFWfhM9DVgTq1PPHADH4w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.9", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.9.tgz", + "integrity": "sha512-YceU74W21fXEHszKBLj16MY0CmvpFKOs60iVrIyW/cCgOPsZJNaB6GVJEtIUqt6AG6EjzKLui5lPRrxAeS328Q==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.9" + } + }, + "node_modules/react-dom/node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-use-measure": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", + "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rechoir/node_modules/resolve": { + "version": "1.22.13", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.13.tgz", + "integrity": "sha512-Dj/cW9zBV2aRj6XzCxuGIs3ESZKv02jixne3q6jFZYZ3/Tg09gHHviSMDL4kObh9pqSahdRHEnDoaP2gn8YLhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.17.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.6.0.tgz", + "integrity": "sha512-q8cvkEp4bRQdWcLvB3vmxpZAlS25Hxws10nCK+rUxUfuB7e430dYZZJ5z5yAoOGBbbDtve8cUg8DFfc3ue12TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", + "ajv-keywords": "^5.1.0" + }, + "bin": { + "schema-utils": "bin/schema-utils.js" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", + "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stats-gl": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz", + "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==", + "license": "MIT", + "dependencies": { + "@types/three": "*", + "three": "^0.170.0" + }, + "peerDependencies": { + "@types/three": "*", + "three": "*" + } + }, + "node_modules/stats-gl/node_modules/three": { + "version": "0.170.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz", + "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==", + "license": "MIT" + }, + "node_modules/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.4", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/synckit": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/thingies": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.1.tgz", + "integrity": "sha512-cV/CMGTK3M4MlnJ/0At6ismOw/A0EEniDNScajjz/Br3c1sqE72YD01rGpPTKwd27wAxI5Pr+6+0w8yofzFRYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/three": { + "version": "0.184.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz", + "integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==", + "license": "MIT" + }, + "node_modules/three-mesh-bvh": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz", + "integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==", + "license": "MIT", + "peerDependencies": { + "three": ">= 0.159.0" + } + }, + "node_modules/three-stdlib": { + "version": "2.36.1", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz", + "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==", + "license": "MIT", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "potpack": "^1.0.1" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, + "node_modules/three-stdlib/node_modules/fflate": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.11.tgz", + "integrity": "sha512-3JyEFWGjFn7zHmoa9+zG1BmW7X2okcmAB+0Cnu9UFbVs/jCBnl2A8o065ZlXiw145K3eBM3uLuzrYXC0RK7eDg==", + "license": "MIT" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/troika-three-text": { + "version": "0.52.5", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.5.tgz", + "integrity": "sha512-Ry3jRhic9pzcY4JduSvRRyDmVOSqEW19gT4vtK+aCiPNVcDlmkxvGG0YbFd36RTDq1wExOupXnvNF/j1oiHHDA==", + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.52.5", + "troika-worker-utils": "^0.52.0", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.52.5", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.5.tgz", + "integrity": "sha512-WsePbcX8RtfidRfsxK1eCZCjF81ZDzAKHH/evLs0hdV2wpoCb0vArGZHdzdOJrSS3k4zfdtbKDaBh8+phkrYnw==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz", + "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-jest": { + "version": "29.4.15", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.15.tgz", + "integrity": "sha512-s2qmEUYFRqbjz1wv0EmccNPVOb45HOFpFuhzQJLKShYVisuTbCFPIIAIV2EuK5rIxb4BejLDbH7dnBZfYjybUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.10", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.8.5", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <9", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-loader": { + "version": "9.6.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.6.2.tgz", + "integrity": "sha512-R4iuczmtgxvtuI556s+hTZ6/7Ee03VCAk/l/M8LY1OAsUgB7YydsCxkgq9D9pKRaD7GJqUi2u8fp9zZP/ufjKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "picomatch": "^4.0.0", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "loader-utils": "*", + "typescript": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "loader-utils": { + "optional": true + } + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ts-poet": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.12.0.tgz", + "integrity": "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA==", + "license": "Apache-2.0", + "dependencies": { + "dprint-node": "^1.0.8" + } + }, + "node_modules/ts-proto": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.17.0.tgz", + "integrity": "sha512-jaULmGDr32uBuRTDQbR0tTH9yGPIw6UE5TyL23F6obNZUAvSi14sRKhDkX1Rgfyxy5l8z493HDfLJa8tkq32EA==", + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.14.1", + "case-anything": "^2.1.13", + "ts-poet": "^6.12.0", + "ts-proto-descriptors": "2.1.0" + }, + "bin": { + "protoc-gen-ts_proto": "protoc-gen-ts_proto" + } + }, + "node_modules/ts-proto-descriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-2.1.0.tgz", + "integrity": "sha512-S5EZYEQ6L9KLFfjSRpZWDIXDV/W7tAj8uW7pLsihIxyr62EAVSiKuVPwE8iWnr849Bqa53enex1jhDUcpgquzA==", + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-rat": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz", + "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==", + "license": "MIT", + "dependencies": { + "zustand": "^4.3.2" + } + }, + "node_modules/tunnel-rat/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.3.tgz", + "integrity": "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/watchpack": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.6.2.tgz", + "integrity": "sha512-67nxRd7I5uAyn+KamYsCqU30iVgCgZ4aj8ZOGzmxs7zNPuvlnoYltm6+6JkOjEcD6WVXdu1BfH+gVQs3FUuW7Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==", + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.112.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.112.0.tgz", + "integrity": "sha512-NIBpsRuCll036haDDt/CfcTDfsWNR9Z782nMRMI9D377BsGcYzZDTNU5TyzhCJzPIcihAxMBaMAhq2WSaDrH8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.26.0", + "es-module-lexer": "^2.1.0", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.13.0", + "schema-utils": "^4.5.0", + "tapable": "^2.3.0", + "watchpack": "^2.6.1", + "webpack-sources": "^3.6.0" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.6.tgz", + "integrity": "sha512-yBWCMvIfUmuhAE8vdqUKzH0vg9kuWN0KeG4vBnqRplUFHRU7lMQjkiJWxVQzvo2BTewqhPhDlMB41rAt2jVA9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.6.tgz", + "integrity": "sha512-HNLRmamRvVavZQ+avceZifmv8hmdUjg43t6MI4SqJDwFdW7RPQwH5vzGhDRZSX59SgfbeHhLnq3g+uooWo7pVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.8.1", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.22.1", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.14.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^5.5.0", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.6.0.tgz", + "integrity": "sha512-EIMmPVvNI0CYXyRPp34F2Qk7W37/BZVZIofS6LAUnrNjCVahS+hO4mxwFJDjQjODTGZDtVzLYoN2+1dYkhk9qA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/webxr-layers-polyfill": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/webxr-layers-polyfill/-/webxr-layers-polyfill-1.1.0.tgz", + "integrity": "sha512-GqWE6IFlut8a1Lnh9t1RPnOXud1rZ7wLPvWp7mqTDOYtgorXqlNMhEnI9EqjU33grBx0v3jm0Oc13opkAdmgMQ==", + "license": "Apache-2.0", + "dependencies": { + "gl-matrix": "^3.4.3" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zustand": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz", + "integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/examples/synthetic_xr/README.md b/examples/synthetic_xr/README.md index 20f98150d9..c928f89025 100644 --- a/examples/synthetic_xr/README.md +++ b/examples/synthetic_xr/README.md @@ -24,7 +24,7 @@ cd "$CAPTURE_ROOT" python -m pip install -e './examples/synthetic_xr[franka]' ``` -Build this checkout's client using [the WebXR build instructions](../../docs/source/development/build_from_source/webxr.rst). The public prebuilt client does not contain this feature. Keep the generated `deps/cloudxr/webxr_client/build/` directory. +Build this checkout's client using [the WebXR build instructions](../../docs/source/development/build_from_source/webxr.rst). Use `npm ci` with the committed client lockfile, then `npm run build`; do not refresh dependency versions while reproducing this candidate. The public prebuilt client does not contain this feature. Keep the generated `deps/cloudxr/webxr_client/build/` directory. ## 1. Start the service From 40a19641416e6926ba40f6366ca66bd965a256f9 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Sun, 18 Oct 2026 12:53:30 +0000 Subject: [PATCH 08/11] Qualify Synthetic XR end-user setup on current Isaac Capture Signed-off-by: Sameer Tangade --- deps/cloudxr/webxr_client/AGENTS.md | 2 + .../tests/mock/cloudxr-mock-alias.ts | 2 + .../tests/playwright/SyntheticXRTest.spec.js | 4 + docs/source/teleop/synthetic_xr.rst | 7 +- examples/synthetic_xr/README.md | 75 ++++++++++-- examples/synthetic_xr/VALIDATION.txt | 115 +++++++++--------- examples/synthetic_xr/pyproject.toml | 2 +- 7 files changed, 140 insertions(+), 67 deletions(-) diff --git a/deps/cloudxr/webxr_client/AGENTS.md b/deps/cloudxr/webxr_client/AGENTS.md index 7aa6663535..922440b657 100644 --- a/deps/cloudxr/webxr_client/AGENTS.md +++ b/deps/cloudxr/webxr_client/AGENTS.md @@ -12,3 +12,5 @@ do not bypass peer checks with `--force` or `--legacy-peer-deps`. Synthetic XR browser checks use the dev server's hot reload. Do not edit or format client files while those checks run: remounting the app invalidates the session and input ownership observations. Synthetic examples must start teleoperation through the existing client handler. Do not hard-code a controller ray to an assumed 3D panel position. + +Long-running synthetic-input tests against MockCloudXR must supply decoded mock frames. Preserve the production decoder startup watchdog and input release on disconnect. diff --git a/deps/cloudxr/webxr_client/tests/mock/cloudxr-mock-alias.ts b/deps/cloudxr/webxr_client/tests/mock/cloudxr-mock-alias.ts index 4b1cfd5cca..3dd7aaa07a 100644 --- a/deps/cloudxr/webxr_client/tests/mock/cloudxr-mock-alias.ts +++ b/deps/cloudxr/webxr_client/tests/mock/cloudxr-mock-alias.ts @@ -60,10 +60,12 @@ declare global { interface Window { __mockCloudXRFail?: (message?: string) => void; __mockCloudXRConnectDelayMs?: number; + __mockCloudXRVideoFrame?: (frameId: number) => void; } } if (typeof window !== 'undefined') { + window.__mockCloudXRVideoFrame = frameId => activeSession?.videoFrameReceived(frameId); window.__mockCloudXRFail = (message = 'Mock transient server/tunnel outage') => { activeSession?.triggerFailure({ name: 'StreamingError', message }); }; diff --git a/deps/cloudxr/webxr_client/tests/playwright/SyntheticXRTest.spec.js b/deps/cloudxr/webxr_client/tests/playwright/SyntheticXRTest.spec.js index ba07cdb15f..838e303b99 100644 --- a/deps/cloudxr/webxr_client/tests/playwright/SyntheticXRTest.spec.js +++ b/deps/cloudxr/webxr_client/tests/playwright/SyntheticXRTest.spec.js @@ -45,6 +45,10 @@ test('generated sweep completes once and an exported trace can be imported', asy await page.goto('http://localhost:8082/?syntheticXR=1&immersiveMode=vr'); await page.waitForFunction(() => Boolean(window.syntheticXR)); await page.click('#startButton'); + await page.waitForFunction(() => window.syntheticXR.lastFrame?.sources.length === 2); + // This long-running input test needs a decoded mock frame, or the real startup + // watchdog correctly disconnects the transport and releases synthetic input. + await page.evaluate(() => window.__mockCloudXRVideoFrame(1)); const run = page.getByRole('button', { name: 'Run controller sweep' }); await expect(run).toBeEnabled(); await run.click(); diff --git a/docs/source/teleop/synthetic_xr.rst b/docs/source/teleop/synthetic_xr.rst index f1b5d8906a..b7e1810ed0 100644 --- a/docs/source/teleop/synthetic_xr.rst +++ b/docs/source/teleop/synthetic_xr.rst @@ -57,7 +57,7 @@ Trigger values close or open the gripper, just as a physical trigger would. Try the example --------------- -Use a configured CloudXR and Isaac Lab host, this preview's built WebXR client +Use the fresh-install recipe linked below, this branch's built WebXR client and the example's optional Franka dependencies. A headset and ADB are not needed; CloudXR's GPU, network and certificate requirements still apply. @@ -72,8 +72,9 @@ The Synthetic XR panel lets you see the input being applied. **You do not need to select Left/Right or operate its movement buttons for this example.** The current candidate validates the session interface with one successful -seed-42 stacking episode on the TeleOp 1.5 / CloudXR 6.3 / Lab compatibility -stack. A separate customer-application acceptance check also verifies a custom +seed-42 stacking episode on a fresh Lab environment with this branch's +Isaac Capture 1.7 SDK and CloudXR 6.3. The browser uses normal sandboxing +and a trusted localhost certificate. A separate customer-application acceptance check also verifies a custom success callback and native dataset export. The earlier preview recording above shows three randomized stacking episodes; it is not a reliability measurement of the new interface. See the diff --git a/examples/synthetic_xr/README.md b/examples/synthetic_xr/README.md index c928f89025..f5023c31d3 100644 --- a/examples/synthetic_xr/README.md +++ b/examples/synthetic_xr/README.md @@ -9,22 +9,83 @@ Run Franka stacking through virtual XR controllers and the native WebXR → Clou ## Supported setup -This integration targets **one local Linux simulation environment** with Lab `cb9628bee016c532feb5fc36c3b012fe707bed1b`, task `IsaacContrib-Stack-Cube-Franka-IK-Abs`, installed Isaac Teleop `1.5+local`, CloudXR `6.3.0`, and this checkout's built WebXR client. These identities describe a compatibility stack, not qualification of other Isaac Capture releases. See [VALIDATION.txt](VALIDATION.txt) for executed results and remaining limits. +This integration targets **one local Linux x86-64 simulation environment** with Isaac Capture built from this branch, Lab `cb9628bee016c532feb5fc36c3b012fe707bed1b`, task `IsaacContrib-Stack-Cube-Franka-IK-Abs`, and CloudXR `6.3.0`. See [VALIDATION.txt](VALIDATION.txt) for exact executed identities and results. -Use an existing configured Lab Python environment, an installed CloudXR runtime, a graphical desktop, and Chrome/Chromium with WebXR support. Follow the product's CloudXR GPU, network, certificate and license setup first. All commands below run on the same host. The Python driver and observer share a local filesystem. Keep CDP and observations on loopback; no headset or ADB is required. +You need a supported NVIDIA GPU/driver, a graphical desktop, Chrome with its normal sandbox enabled, and the product's CloudXR network and license prerequisites. The Python driver and observer share a local filesystem. Keep CDP and observations on loopback; no headset or ADB is required. -Set these paths in each terminal, activate your configured Lab environment, and work from the Isaac Capture checkout: +### Install in a fresh environment + +Use this candidate's complete source checkout or source archive as `CAPTURE_ROOT`. The public prebuilt client does not contain this feature. Install Git, Git LFS, [uv](https://docs.astral.sh/uv/getting-started/installation/), Node.js 20 or later, and the [Isaac Capture source-build prerequisites](../../docs/source/development/build_from_source/index.rst). Use a new directory for Lab: ```bash export CAPTURE_ROOT=/absolute/path/to/IsaacCapture export LAB_ROOT=/absolute/path/to/IsaacLab export CLOUDXR_INSTALL_DIR=/absolute/path/to/cloudxr export SYNTHETIC_RUN=/absolute/path/to/a/new/run + +git clone https://github.com/isaac-sim/IsaacLab.git "$LAB_ROOT" +git -C "$LAB_ROOT" checkout cb9628bee016c532feb5fc36c3b012fe707bed1b +cd "$LAB_ROOT" +uv sync --locked --extra teleop --extra isaacsim --extra ov +source .venv/bin/activate +``` + +The pinned Lab checkout manages its own native dependencies and explicit dependency overrides. Use its locked environment rather than combining packages from an existing installation. Install this branch's SDK **after** Lab setup; another `uv sync` would restore Lab's older SDK selection. + +Build the SDK wheels, or use the matching prebuilt Linux/Python 3.12 wheels distributed with this candidate: + +```bash +cd "$CAPTURE_ROOT" +cmake -B build -DISAAC_TELEOP_PYTHON_VERSION=3.12 \ + -DBUILD_VIZ=OFF -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF \ + -DENABLE_CLOUDXR_EXP_BUNDLE=OFF -DENABLE_CLOUDXR_BUNDLE_CHECK=ON \ + -DCMAKE_INSTALL_PREFIX="$CAPTURE_ROOT/install" +cmake --build build --parallel 6 +cmake --install build +uv pip install 'isaaccapture[retargeters-lite,cloudxr,ui]==1.7+local' \ + --find-links="$CAPTURE_ROOT/install/wheels" +uv pip install './examples/synthetic_xr[franka]' +``` + +Both the `isaaccapture` wheel and its `isaacteleop` transition wheel must be available in `install/wheels`. The feature's `[franka]` extra supplies its browser transport and numerical dependencies. This recipe builds the SDK without Televiz; the Franka example uses Lab's Kit viewport. + +Build the client with its committed dependency lockfile: + +```bash +cd "$CAPTURE_ROOT" +printf 'CXR_HOST_VOLUME_PATH=%s\n' "$CLOUDXR_INSTALL_DIR" > deps/cloudxr/.env +source scripts/setup_cloudxr_env.sh +scripts/download_cloudxr_sdk.sh +cd deps/cloudxr/webxr_client +npm ci +npm run build cd "$CAPTURE_ROOT" -python -m pip install -e './examples/synthetic_xr[franka]' ``` -Build this checkout's client using [the WebXR build instructions](../../docs/source/development/build_from_source/webxr.rst). Use `npm ci` with the committed client lockfile, then `npm run build`; do not refresh dependency versions while reproducing this candidate. The public prebuilt client does not contain this feature. Keep the generated `deps/cloudxr/webxr_client/build/` directory. +Keep `deps/cloudxr/webxr_client/build/`. In each subsequent terminal, activate `$LAB_ROOT/.venv/bin/activate` and set the same four paths above. + +### Trust the local service certificate + +For localhost development on Linux, generate the service's certificate before opening Chrome: + +```bash +python -c 'import os; from pathlib import Path; from isaaccapture.cloudxr.wss import cert_paths_from_dir, ensure_certificate; ensure_certificate(cert_paths_from_dir(Path(os.environ["CLOUDXR_INSTALL_DIR"]) / "certs"))' +``` + +Import `server.crt` as a trusted self-signed **server** certificate using Chrome's certificate manager. Alternatively, with `libnss3-tools` installed, follow [Chromium's Linux certificate instructions](https://chromium.googlesource.com/chromium/src/+/main/docs/linux/cert_management.md): + +```bash +nss_dir="$HOME/.local/share/pki/nssdb" +if [[ -d "$HOME/.pki/nssdb" ]]; then nss_dir="$HOME/.pki/nssdb"; fi +mkdir -p "$nss_dir" +if [[ ! -f "$nss_dir/cert9.db" ]]; then + certutil -N --empty-password -d "sql:$nss_dir" +fi +certutil -A -d "sql:$nss_dir" -n synthetic-xr-localhost -t 'P,,' \ + -i "$CLOUDXR_INSTALL_DIR/certs/server.crt" +``` + +Trust only the certificate generated on your host and keep `server.key` private. Restart the dedicated browser after changing trust. The example uses `https://localhost`; a remote hostname needs a certificate valid for that hostname. To remove this trust later, run `certutil -D -d "sql:$nss_dir" -n synthetic-xr-localhost`. ## 1. Start the service @@ -33,7 +94,7 @@ Check that another service does not own the configured CloudXR ports. Run in a d ```bash export TELEOP_WEB_CLIENT_STATIC_DIR="$CAPTURE_ROOT/deps/cloudxr/webxr_client/build" export TELEOP_OOB_HUB_ONLY=1 -python -m isaacteleop.cloudxr.service run \ +python -m isaaccapture.cloudxr.service run \ --cloudxr-install-dir "$CLOUDXR_INSTALL_DIR" \ --cloudxr-env-config "$LAB_ROOT/source/isaaclab_teleop/isaaclab_teleop/cloudxrjs-cloudxr.env" \ --setup-oob --host-client @@ -57,7 +118,7 @@ SYNTHETIC_XR_VISIBLE=1 python -m isaaccapture_examples.synthetic_xr.franka.obser --num_demos 1 --xr --viz kit --cloudxr_env none --no-auto_launch_cloudxr env.seed=42 ``` -The observation directory must not exist. Accept Kit's license prompt if this environment has not done so. After accepting its terms, unattended runs can set `OMNI_KIT_ACCEPT_EULA=YES`. Wait for Lab's XR/scene initialization and the loopback endpoint `http://127.0.0.1:8765/state`. No robot motion starts until the task driver starts teleoperation. +The observation directory must not exist. Accept Kit's license prompt if this environment has not done so. After accepting its terms, unattended runs can set `OMNI_KIT_ACCEPT_EULA=YES`. Resolve any first-launch desktop or Kit setup dialogs before starting the task; decline optional telemetry if you do not want it. Wait for Lab's XR/scene initialization and the loopback endpoint `http://127.0.0.1:8765/state`. No robot motion starts until the task driver starts teleoperation. ## 3. Connect the browser diff --git a/examples/synthetic_xr/VALIDATION.txt b/examples/synthetic_xr/VALIDATION.txt index 110a3b5724..7a73ed512b 100644 --- a/examples/synthetic_xr/VALIDATION.txt +++ b/examples/synthetic_xr/VALIDATION.txt @@ -1,61 +1,64 @@ -Synthetic XR Teleoperation 0.2.0 release candidate — validation, 2026-10-18 +Synthetic XR Teleoperation 0.2.1 — end-user validation, 2026-10-18 -Supported configuration exercised: -- This feature branch's desktop WebXR client, IWER and OOB/CDP CONNECT. -- Isaac Teleop 1.5+local native backend; CloudXR Runtime 6.3.0. -- Isaac Lab cb9628bee016c532feb5fc36c3b012fe707bed1b. -- IsaacContrib-Stack-Cube-Franka-IK-Abs, seed 42, one environment. -- Local Linux host, SwiftShader/CPU compositing, 512x448 per eye. -- No ADB or physical headset. Native retargeting and IK remain in Lab. +Executed configuration: +- SDK built from our branch refreshed onto upstream main 22026461cceb238fc0f7b3d4aecd3d970d444791. +- Isaac Capture 1.7+local, isaacteleop transition distribution 1!1.7+local, + CloudXR Runtime 6.3.0, Isaac Sim 6.1.0.0. +- Fresh public Isaac Lab checkout cb9628bee016c532feb5fc36c3b012fe707bed1b; + fresh uv environment, no imports from the earlier prepared Lab environment. +- Ubuntu x86-64, RTX PRO 6000 Blackwell, driver 595.58.03, Python 3.12.14. +- Google Chrome 155.0.8059.84: normal namespace/seccomp sandbox verified, + exact localhost server certificate trusted; no sandbox/certificate bypass flags. +- Desktop IWER, real OOB hub, CONNECT/CDP, native CloudXR and Lab retargeting/IK. +- SwiftShader/CPU compositing, H.264, 512x448 per eye; functional scope only. +- IsaacContrib-Stack-Cube-Franka-IK-Abs, seed 42, one environment per run. -Fresh stacking through the public FrankaTaskSession API: -Run 7c28fb4a6b2d47d1901f332f127718d4: PASS. -One native HDF5 episode, 1428 samples; 197 correlated browser/native commands. -Maximum position difference 1.6269140171458334e-7 metres. -Every recorded action matches the native feedback; final ten native states -satisfy independent three-cube stack geometry. Native environment closed, -worker exit 0, no forced shutdown. Full host/browser video 152.25 seconds. +Stacking via the documented public task API: +Run 27c78abcefb9443d9e09316c36f7c335: PASS. +One native episode, 1453 samples, 200 correlated browser/native commands. +Maximum position difference 1.62603325e-07 metres. +All recorded actions match native feedback. Final ten native states satisfy +independent three-cube geometry. Native environment closed; worker exit 0; +no forced shutdown. Full same-run Lab/browser recording: 157.200000 seconds. -Customer task extension acceptance (separate from the stacking tutorial): -Run 79501bd58db34857969922c459d605d9: PASS. -A separate pick-and-place application uses only the documented session API and -success callback. It exports one native HDF5 episode with 761 samples and 108 -correlated commands; maximum position difference 1.6074436996170352e-7 metres. -Lift 0.15978 metres, final XY error below 0.000895 metres; last twenty native -states confirm placement, settling, open fingers and tool retraction. Worker -exit 0, no forced shutdown. Full host/browser video 223.35 seconds. -Lab records policy observations before a step and rigid-body states after it; -the verifier aligns these and uses raw finger joints (the processed second -finger observation has a negative sign). +Custom task acceptance (kept separate from the stacking tutorial): +Run 9717f37107884bc3924e9f5839ff6344: PASS. +The application uses only FrankaTaskSession and a custom success callback. +One native episode, 752 samples, 109 correlated commands. +Maximum position difference 1.57192716e-07 metres. +Lift 0.159786 m; final XY error 0.000906 m. +Last twenty native states verify placement, settling, open fingers and retraction. +Native environment closed; worker exit 0; no forced shutdown. +Full same-run Lab/browser recording: 125.350000 seconds. -Native driver-crash control: -Run e767207f7f6d4097aa2f4fdd685cefa8: expected failure detected. -The driver exits abruptly with status 23 after attachment. With a three-second -lease, the native observer detects expiration and exits with status 1, no -successful export and no forced shutdown. The normal default lease is 30 seconds. +Verification and packaging: +Both native HDF5 datasets are checked against feedback and command receipts, +with native pre-step observations aligned to post-step states. Both videos fully +decode. Selected frames show grasp, lift, transfer and release in both views; +this is sampled visual inspection, not exhaustive video-model evaluation. +The final feature Python modules match those executed byte for byte. Version, +setup documentation and browser-only mock fixtures changed after native runs; +production browser source is unchanged. Exact wheel/build identities, final +repository checks, independent review and archive checks accompany this package. +25 Python controls, 130 JavaScript tests and four browser checks pass. -Evidence and checks: -Native datasets, command receipts, logs, source identities, independent numeric -verification and full recordings accompany the candidate. Selected-frame visual -inspection confirms approach, grasp, transfer and release in both views; this -is not exhaustive frame-by-frame visual model evaluation. -Unit controls cover invalid poses, stale/mismatched feedback, strict callback -results, driver ownership, expired heartbeat, completion and cleanup failures. -Final repository checks and package identities are in the release manifest. -The browser implementation and earlier MCAP replay are unchanged; no fresh MCAP -or browser-only transport-mock qualification is implied by these native runs. - -Limits and retained failures: -This is validation on a prepared host, not a clean-machine installation study. -Host-specific browser test flags bypassed certificate and sandbox enforcement; -the documented customer setup requires a trusted certificate and normal sandbox. -A missing optional dependency in the test harness, an incorrect frame-array -assumption and native Kit process teardown exposed three failed attempts. Those -failures are retained; the latter two led to regression tests and a supervised -observer. The successful stacking wheel differs from the subsequent customer -wheel only by removal of an unused Python import in observer_process.py. -Repeated success/reliability, arbitrary robot/task adapters, real robots, -physical Pico/Quest sensing and current Isaac Capture 1.6 native compatibility -are not qualified. Earlier three-episode footage demonstrates the preceding -preview, not repetition testing of the new session API. -No official SDK release or upstream/main merge is implied. +Retained failures and scope: +The first native attempt timed out without advancing/exporting while first-launch +Kit/desktop dialogs obscured the session. It was stopped and preserved before +resolving the setup prompts and using a new run directory. The long browser-only +sweep test exposed a missing decoded mock frame; the real startup watchdog +correctly disconnected and released input. Its fixture now supplies that frame. +No production timeout or input-release behavior was weakened. +Lab's managed uv configuration intentionally overrides several upstream dependency +pins; the raw dependency checker reports these conflicts and two excluded optional +packages. This is an exercised pinned Lab environment, not a globally conflict-free +Python stack. No Error/Traceback lines occur in the two successful native Lab logs. +Fresh environment on an existing GPU/desktop host, with download caches reused; +not a new operating-system installation or a blinded first-time-user study. +The same assistant authored the custom application with prior project context. +Earlier driver-crash and MCAP checks remain historical evidence; they were not +rerun on 1.7. Repeated reliability, other SDK versions/robots, physical sensing, +hand/body/glove devices and real-robot safety remain outside this qualification. +Earlier three-episode footage in the tutorial is explicitly labelled historical. +This is our branch delivery, not an official NVIDIA SDK release. NVIDIA main +must not be modified or merged into as part of this work. diff --git a/examples/synthetic_xr/pyproject.toml b/examples/synthetic_xr/pyproject.toml index 0f9282249c..562567d2a0 100644 --- a/examples/synthetic_xr/pyproject.toml +++ b/examples/synthetic_xr/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "hatchling.build" [project] name = "isaaccapture-examples-synthetic-xr" -version = "0.2.0" +version = "0.2.1" description = "Synthetic XR Teleoperation desktop examples" readme = "README.md" license = "Apache-2.0" From de640d3c9dc8ffff67ccb2b078d82638e6975979 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Tue, 20 Oct 2026 15:45:43 +0000 Subject: [PATCH 09/11] fix: report terminal Synthetic XR observer failures immediately Signed-off-by: Sameer Tangade --- .../synthetic_xr/franka/session.py | 7 +- .../synthetic_xr/tests/test_task_session.py | 69 +++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py index 74d3cd8bc3..da52e1eac5 100644 --- a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py @@ -98,14 +98,15 @@ def state(self, max_age=5): value = self.client.state(max_age) except (URLError, OSError): value = json.loads((self.observation_dir / "sim-state.json").read_text()) - # Only an orderly terminal snapshot can outlive its HTTP server. - if value.get("phase") != "completed" or value.get("completion_error"): + # Retry only until the supervisor publishes a terminal snapshot. + if value.get("phase") != "completed": raise ObserverUnavailable( "Observer disconnected before orderly completion" ) from None - value = validate_observation(value) + # A failed worker may exit before publishing a complete observation schema. if value.get("phase") == "completed" and value.get("completion_error"): raise RuntimeError("Observer failed: " + value["completion_error"]) + value = validate_observation(value) if Path(value["run_dir"]).resolve() != self.observation_dir: raise RuntimeError("Feedback belongs to a different observation directory") if self.run_id is not None and value["run_id"] != self.run_id: diff --git a/examples/synthetic_xr/tests/test_task_session.py b/examples/synthetic_xr/tests/test_task_session.py index cd7bf7c3c0..f06067c2cb 100644 --- a/examples/synthetic_xr/tests/test_task_session.py +++ b/examples/synthetic_xr/tests/test_task_session.py @@ -198,6 +198,75 @@ def test_export_requires_native_completion_and_never_writes_success(self): with self.assertRaises(TimeoutError): session.wait_for_export(timeout=0.001) + def test_terminal_worker_error_is_reported_without_polling(self): + for offline in (False, True): + for initialized in (False, True): + with ( + self.subTest(offline=offline, initialized=initialized), + TemporaryDirectory() as directory, + ): + root = Path(directory) + if initialized: + value = observation(root) + value["exported_successful_episodes"] = 1 + (root / "sim-state.json").write_text(json.dumps(value)) + value = completion_snapshot(root, "one", 23) + (root / "sim-state.json").write_text(json.dumps(value)) + session = FrankaTaskSession( + observation_dir=root, output=root / "task" + ) + session.client.state = ( + Mock(side_effect=URLError("disconnected")) + if offline + else Mock(return_value=value) + ) + with ( + patch( + "isaaccapture_examples.synthetic_xr.franka.session.time.sleep", + side_effect=AssertionError( + "Terminal failures must not retry" + ), + ) as sleep, + self.assertRaisesRegex( + RuntimeError, + "Observer failed: Worker exited with status 23", + ), + ): + session.wait_for_export() + sleep.assert_not_called() + self.assertFalse((root / "task/result.json").exists()) + + def test_export_retries_nonterminal_snapshot_until_orderly_completion(self): + with TemporaryDirectory() as directory: + root = Path(directory) + output = root / "task" + output.mkdir() + value = observation(root) + (root / "sim-state.json").write_text(json.dumps(value)) + session = FrankaTaskSession(observation_dir=root, output=output) + session.run_id = "one" + session.client.state = Mock(side_effect=URLError("disconnected")) + session.mark = Mock() + + def finish_worker(_): + value.update( + exported_successful_episodes=1, native_environment_closed=True + ) + (root / "sim-state.json").write_text(json.dumps(value)) + (root / "sim-state.json").write_text( + json.dumps(completion_snapshot(root, "one", 0)) + ) + + with patch( + "isaaccapture_examples.synthetic_xr.franka.session.time.sleep", + side_effect=finish_worker, + ) as sleep: + self.assertEqual(session.wait_for_export()["phase"], "completed") + sleep.assert_called_once() + self.assertEqual( + json.loads((output / "result.json").read_text())["status"], "PASS" + ) + def test_invalid_tool_and_gripper_inputs_never_send_motion(self): session = FrankaTaskSession(observation_dir="/tmp/observer", output="/tmp/task") session.controller = Mock() From 930beccc3226966e1d0d22ed0c1be35cf5e44817 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Sat, 24 Oct 2026 08:31:08 +0000 Subject: [PATCH 10/11] Add shared Synthetic XR task adapters and validated episode guards Signed-off-by: Sameer Tangade --- .github/workflows/pre-commit.yaml | 17 + docs/AGENTS.md | 3 + docs/source/teleop/synthetic_xr.rst | 7 + examples/synthetic_xr/README.md | 37 +- examples/synthetic_xr/VALIDATION.txt | 65 ++- .../synthetic_xr/franka/native_backend.py | 226 +++++++++++ .../franka/observe_record_demos.py | 23 +- .../synthetic_xr/franka/pick_place_logic.py | 45 +++ .../synthetic_xr/franka/pick_place_success.py | 69 ++++ .../synthetic_xr/franka/session.py | 79 +++- .../synthetic_xr/franka/task_hooks.py | 20 + .../synthetic_xr/franka/task_plan.py | 145 +++++++ .../synthetic_xr/franka/xr_backend.py | 88 +++++ .../franka/xr_controller_bridge.py | 81 ++-- .../synthetic_xr/tests/test_input_backends.py | 178 +++++++++ .../tests/test_native_settling.py | 212 ++++++++++ .../synthetic_xr/tests/test_shared_task.py | 335 ++++++++++++++++ .../synthetic_xr/tests/test_task_guards.py | 373 ++++++++++++++++++ .../synthetic_xr/tests/test_task_session.py | 3 + 19 files changed, 1966 insertions(+), 40 deletions(-) create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/native_backend.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/pick_place_logic.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/pick_place_success.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_plan.py create mode 100644 examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_backend.py create mode 100644 examples/synthetic_xr/tests/test_input_backends.py create mode 100644 examples/synthetic_xr/tests/test_native_settling.py create mode 100644 examples/synthetic_xr/tests/test_shared_task.py create mode 100644 examples/synthetic_xr/tests/test_task_guards.py diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 23e299a75b..17e6e4c1fa 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -39,3 +39,20 @@ jobs: python-version: "3.12" - name: Test preview selection run: python3 -m unittest discover -s tests/python/ci -p test_docs_preview.py -v + + synthetic-xr-example-tests: + name: Test Synthetic XR task contracts + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Install example package + run: python -m pip install './examples/synthetic_xr[franka]' + - name: Test task contracts without a GPU + run: python -m unittest discover -s examples/synthetic_xr/tests -v diff --git a/docs/AGENTS.md b/docs/AGENTS.md index dd067393ff..9e44db2ae0 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -132,3 +132,6 @@ that layer: do not add a landing page where a caption was asked for. Lead with the automated robot task and show where human and generated input join the same native path. Keep the phase-one guide to one main page; defer manual controller and recording walkthroughs rather than adding reference pages. + +Keep input-reuse explanations under “Bring your own task logic” brief. Put +adapter contracts in the source example, retaining stacking as the main tutorial. diff --git a/docs/source/teleop/synthetic_xr.rst b/docs/source/teleop/synthetic_xr.rst index b7e1810ed0..de9b7f13a8 100644 --- a/docs/source/teleop/synthetic_xr.rst +++ b/docs/source/teleop/synthetic_xr.rst @@ -88,6 +88,13 @@ Keep the virtual controller and native pipeline; change the decisions that drive them. For example, a pick-and-place script could choose a destination region instead of another cube as the placement target. +**Reuse task logic across input sources.** Keep approach, grasp, transfer and +release decisions separate from input generation. The example's +:download:`shared task logic <../../../examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_plan.py>` +uses input-specific adapters for XR controller poses, keyboard events or gamepad +values. XR uses the WebXR/CloudXR path; the keyboard/gamepad adapters target +Lab's native input path. See the example README for integration contracts. + .. code-block:: text Your task logic diff --git a/examples/synthetic_xr/README.md b/examples/synthetic_xr/README.md index f5023c31d3..ba40d10b57 100644 --- a/examples/synthetic_xr/README.md +++ b/examples/synthetic_xr/README.md @@ -153,20 +153,53 @@ Import `FrankaTaskSession` from `isaaccapture_examples.synthetic_xr.franka.sessi | Operation | Contract | | --- | --- | | `observe()` | Fresh schema-v1 dictionary; rejects stale feedback, a restarted observer, or a mismatched observation directory. | -| `move_tool(position, quaternion, seconds=2)` | Franka `ee_frame` target in world metres/Z-up; quaternion `[x,y,z,w]`, unit length. Interpolates XR input and checks native acknowledgement and tool convergence. | +| `move_tool(position, quaternion, seconds=2)` | Franka `ee_frame` target in world metres/Z-up; quaternion `[x,y,z,w]`, unit length. Checks the endpoint before input, then requires measured position within 0.01 m and orientation within 0.05 radians. | | `set_gripper(closed=True)` | Boolean grip decision, converted to XR trigger; this does not write robot joints. | | `wait(seconds)` | Wait in simulation seconds with a bounded wall-clock timeout. | | `mark(name)` | Save a named observation to the task trace. | | `wait_for_export(timeout=30)` | Wait for the native successful episode and orderly observer completion. Does not set a success flag. Use `--num_demos 1`. | -Observation fields: `run_id` identifies the observer, `episode_id` changes on reset, `steps` counts physics steps, `simulation_dt` is seconds/step, and `timestamp` is host Unix time. `ee_positions_world[0][0]` and `ee_quaternions_world[0][0]` describe the tool. `cubes[name]` contains `position_world[0]`, `quaternion_world[0]`, and `linear_velocity_world[0]`. Positions are metres; velocities are metres/second; all quaternions are xyzw. `joint_names` maps `joints[0]`. Select additional scene objects with the observer's `--objects name ...`; the `cubes` field name is retained for schema-v1 compatibility. +Observation fields: `run_id` identifies the observer, `episode_id` changes on reset, `steps` counts environment steps, `simulation_dt` is seconds/environment step, and `timestamp` is host Unix time. `ee_positions_world[0][0]` and `ee_quaternions_world[0][0]` describe the tool. `cubes[name]` contains `position_world[0]`, `quaternion_world[0]`, and `linear_velocity_world[0]`. Positions are metres; velocities are metres/second; all quaternions are xyzw. `joint_names` maps `joints[0]`. Select additional scene objects with the observer's `--objects name ...`; the `cubes` field name is retained for schema-v1 compatibility. The adapter owns the `panda_hand` → tool offset (0.1034 m along local Z) and XR/frame transforms. It remains specific to this Franka configuration. Targets are checked for finite values and the declared workspace; this is not a collision-avoidance or reachability planner. Native episode completion ends movement immediately, including any remaining interpolation; inspect the returned observation before issuing another action. Only one task driver may attach to an observer. Use a new observation directory for every run; a failed attachment does not take over an existing driver. +The session binds the episode after startup. An unexpected explicit or automatic reset, or a regressing environment step, releases input and fails the task; start a fresh session rather than resuming its old trajectory. Native successful export is terminal and does not rearm motion after a recorder reset. These are asynchronous feedback guards: a reset after the last check can race with one command already in flight. Command receipts require a matching action at an environment step later than the pre-send observation. This is fresh-step value correlation, not a unique transport acknowledgement for identical commands; measured tool convergence is checked separately. + For your own task, use the shipped [stack_logic.py](python/isaaccapture_examples/synthetic_xr/franka/stack_logic.py) as the runnable starting point and replace its decisions. Define a trusted Python callback `is_success(observation) -> bool` and start the observer with `--success my_task:is_success`; make `my_task` importable in the Lab environment. The callback receives a detached snapshot after each step. Use `episode_id` to reset any history. Exceptions and non-boolean returns abort the run. The callback is application code, not a sandbox. The native recorder still requires its configured consecutive success steps (`--num_success_steps`, default 10), records state/actions, marks success and exports. An application callback changes the success criterion, not the motion pipeline. Include any required grasp/lift history, settling and release checks. A driver finishing its commands does not establish success. Without `--success`, the original native stacking criterion remains active. +## Reuse task decisions across inputs + +[`task_plan.execute`](python/isaaccapture_examples/synthetic_xr/franka/task_plan.py) contains the input-independent pick-and-place decisions. It calls the `TaskBackend` interface: `observe`, `move_tool`, `set_gripper`, `wait` and `neutral`. `neutral` stops motion and releases button edges while retaining the gripper's open/closed state. Task completion is separate from the application's success criterion and native export. + +The packaged [`XRBackend`](python/isaaccapture_examples/synthetic_xr/franka/xr_backend.py) wraps `FrankaTaskSession`. To try pick-and-place with the same service/browser setup, use a **new run directory**, add the following options to the observer command in step 2, then replace step 4 with: + +```bash +# Additional observer arguments (before the Lab arguments): +# --success isaaccapture_examples.synthetic_xr.franka.pick_place_success:is_success +# Replace the default recorder success count with --num_success_steps 300. +python -m isaaccapture_examples.synthetic_xr.franka.pick_place_logic \ + --observation-dir "$SYNTHETIC_RUN/observations" \ + --output "$SYNTHETIC_RUN/logic" +``` + +This example requires `--num_demos 1`: the pinned Lab recorder exits after export before its reset branch, retaining the completed episode for final observations. The driver and callback share a fixed destination `(0.45, -0.20)` metres in XY and reject a scene where it is occupied or too close. The callback requires a lifted object, placement, open fingers and at least 0.16 m vertical retreat toward the 0.18 m target. Use 300 consecutive success steps to leave margin for the remaining retreat, command latency and the three-second neutral wait. This count is a conservative example setting, not a general timing guarantee. The adapter independently requires the full three-second neutral wait and rejects premature export. Observer `steps` and `simulation_dt` both describe environment steps (`env.step_dt`), so the wait uses that same timebase. Keep this recorder count for the example; the default stacking count can export too early. + +[`NativeBackend`](python/isaaccapture_examples/synthetic_xr/franka/native_backend.py) contains the corresponding **Lab-native integration adapters**, with bounded target settling, reset/staleness checks and input release on failure. These are callback-based adapters, not Capture keyboard/gamepad devices or a ready-to-run native launcher. The application owns its Lab observer, OS input device and shutdown: + +```python +from isaaccapture_examples.synthetic_xr.franka.native_backend import NativeBackend +from isaaccapture_examples.synthetic_xr.franka.task_plan import execute + +backend = NativeBackend("keyboard", read_observation, send_input, run_id) +result = execute(backend, record_phase) # Use "gamepad" with a gamepad sender. +``` + +- `read_observation()` supplies fresh dictionaries: `run_id`, `episode_id`, monotonic `step`, `sim_time`, Unix `time`, `ee`, `ee_quaternion`, `object`, `object_quaternion`, `object_velocity`, `joints`, `action`, `terminated` and `truncated`. Vectors are world metres/Z-up; `quaternion_order` must be `"xyzw"`. `tool_frame` must identify `ee_frame`, index `0`, body `panda_hand`, offset `[0, 0, 0.1034]`. Joints end with the two Franka fingers; the action ends with the native gripper command. The adapter validates this scene contract before motion. +- Keyboard `send_input(keys)` applies a complete held-key set through an existing mechanism such as XTEST: release previous keys, then hold the supplied keys; `[]` releases all. Gamepad `send_input(values)` applies normalized `lx/ly/rx/ry`, discrete `hx/hy` D-pad axes and binary `x`; omitted values mean zero. Use an existing device mechanism such as evemu with a stock Xbox360 profile. Calls must be bounded and report errors; callbacks are not preempted by the adapter's cooperative timeouts. +- These mappings were exercised with Lab `91a0906f93802eb859192dcb0bca879f891e92d6`, `IsaacContrib-Lift-Cube-Franka-IK-Rel`, keyboard sensitivity `0.8` and gamepad sensitivity `0.5`. They use native `Se3Keyboard`/`Se3Gamepad`, bypassing WebXR/CloudXR. Validate the mapping and observation contract before using another Lab revision or scene. Synthetic XR retains the documented Capture path. + ## Stop and troubleshoot The session context releases controller input and requests observer shutdown on exceptions. The observer supervisor bounds the complete run to 900 seconds by default (`--session-timeout`); timeout or forced shutdown remains a failure. For an externally interrupted driver, create `$SYNTHETIC_RUN/observations/stop` to stop the observer, or interrupt its terminal. Close the dedicated browser and interrupt the service terminal when finished. Once a session attaches, the observer stops if it receives no driver heartbeat for 30 seconds (`--driver-timeout`). Session operations refresh this heartbeat. Long application computations must call `observe()` periodically or configure an appropriate timeout. This watchdog stops the simulation; it is not a real-robot safety system. A crashed driver cannot perform normal browser cleanup. diff --git a/examples/synthetic_xr/VALIDATION.txt b/examples/synthetic_xr/VALIDATION.txt index 7a73ed512b..c50b0944e3 100644 --- a/examples/synthetic_xr/VALIDATION.txt +++ b/examples/synthetic_xr/VALIDATION.txt @@ -1,5 +1,68 @@ +24 October 2026 — shared-action example integration + +The input-independent task and XR/native adapters are packaged from the exercised +shared pick-and-place experiment. The tutorial remains focused on stacking; +keyboard/gamepad are optional Lab-native callback integration examples, not new +Capture device support. No host credentials, device creation, QA verdict code or +host-specific launch paths are included in the package. + +Prepared-host evidence before packaging/rebase: +- Repaired XR feature ceb6941c: stacking 1966 native samples/190 command + correlations; pick-and-place 23/23 checks,1268 samples/109 correlations, + 0.671 mm placement error. One successful native episode per task. +- Native keyboard: 40/40 checks,1771 samples,1.342 mm placement error. +- Native gamepad: 41/41 checks,2915 samples,1.774 mm placement error. +- Native common adapter: 25 CPU controls and Claude source review accepted. + XR four-fix patch: Claude and Codex source reviews accepted. +- Keyboard and gamepad initially failed target settling; those attempts remain + retained. Bounded correction preserves the original pose tolerances/budgets. +- Native keyboard/gamepad used AutoData953cd377 and Lab91a0906f, relative IK; + XR used the prepared Capture83e2598e backend and Labcb9628be, absolute IK. + XR's installed example wheel and rebuilt client contained the repaired feature. +- Primary assistant reviewed sampled video frames, full decode and independent + native telemetry/negative controls. No separate model whole-video review. + +These are single final passes on a prepared x86 host, not reliability, physical +sensing or full current-main SDK-build qualification. The packaging/rebase is +checked separately; prior recordings are not claimed to execute the new Git head. +The packaged/rebased example was then exercised on the same prepared native +Capture83e2598e/Labcb9628be backend with the rebuilt client. First motion-only +attempt and diagnostic retry failed native export: the callback cached an old +scene under a new reset ID on the paused render path. Both failures are retained. +The observer now refreshes the scene at each custom-success evaluation boundary. +A regression fails against the old package and passes against the fixed wheel. + +Fresh direct packaged XR pick-and-place after that repair: +- One native episode exported; native worker exited normally, task PASS. +- 23/23 independent dataset/input/placement checks;1412 native samples, + 109 browser/native command correlations;0.310 mm placement error. +- Native success began at step1113; final neutral wait required step1194, + task checkpoint1196; native export1412 after300 success steps. +- Same active episode; every input acknowledgement has a later native step. +- Browser-left/Lab-right video decodes cleanly; sampled carry/release frames + agree with native telemetry. This is primary review, not whole-video model review. +- Fixed destination, export guard and shared task ran from the installed wheel; + keyboard/gamepad runtime evidence remains the prepackaging runs above. +- Bounded shared-source and reset-snapshot fix reviews accepted by Claude; + the earlier four XR guard fixes also had Codex review. Reviews are code-level, + not blanket end-to-end certification of every feature or target platform. +- 85 installed Python checks,130 client checks,4 browser checks, production/type + builds, strict docs and repository pre-commit. Temporary trust and owned + processes were cleaned up; unrelated host workloads were preserved. + +This validates the final example modules on the prepared backend. It does not +claim a full SDK rebuild from rebased main or repeated-run reliability. + +--- Retained end-user installation evidence --- + Synthetic XR Teleoperation 0.2.1 — end-user validation, 2026-10-18 +Historical native evidence: these runs predate the task-guard corrections for +endpoint admission, measured orientation, episode reset and fresh-step action +correlation. Those corrections have CPU regression coverage and the prepared-host native +recheck recorded below. Do not treat the earlier results as native qualification +of subsequent source changes. + Executed configuration: - SDK built from our branch refreshed onto upstream main 22026461cceb238fc0f7b3d4aecd3d970d444791. - Isaac Capture 1.7+local, isaacteleop transition distribution 1!1.7+local, @@ -36,7 +99,7 @@ Both native HDF5 datasets are checked against feedback and command receipts, with native pre-step observations aligned to post-step states. Both videos fully decode. Selected frames show grasp, lift, transfer and release in both views; this is sampled visual inspection, not exhaustive video-model evaluation. -The final feature Python modules match those executed byte for byte. Version, +At that validation checkpoint, the feature Python modules matched those executed byte for byte. Version, setup documentation and browser-only mock fixtures changed after native runs; production browser source is unchanged. Exact wheel/build identities, final repository checks, independent review and archive checks accompany this package. diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/native_backend.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/native_backend.py new file mode 100644 index 0000000000..4ae5830a5d --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/native_backend.py @@ -0,0 +1,226 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Translate common tool targets into existing XTEST/evemu inputs only.""" + +import logging +import math +import time +from scipy.spatial.transform import Rotation + + +_LOG = logging.getLogger("isaaccapture.examples.synthetic_xr.franka.native_backend") + + +class NativeBackend: + """Lab Se3Keyboard/Se3Gamepad mapping; the caller owns read/send and devices.""" + + def __init__(self, kind, read, send, run_id, cancelled=lambda: False): + if kind not in {"keyboard", "gamepad"}: + raise ValueError("Unsupported native backend") + self.name, self.read, self.send, self.run_id = kind, read, send, run_id + self.cancelled = cancelled + self.last_step = -1 + self.episode = None + self.moves = [] + + def observe(self, *, deadline=None): + local_deadline = time.monotonic() + 3 + deadline = local_deadline if deadline is None else min(local_deadline, deadline) + while time.monotonic() < deadline: + if self.cancelled(): + raise RuntimeError("Cancelled") + s = self.read() + if s.get("run_id") != self.run_id: + raise RuntimeError("Foreign observation run") + if s.get("tool_frame") != { + "sensor": "ee_frame", + "index": 0, + "body_name": "panda_hand", + "offset_position": [0.0, 0.0, 0.1034], + }: + raise RuntimeError("Unqualified tool frame") + if self.episode is None: + self.episode = s["episode_id"] + if s["episode_id"] != self.episode: + raise RuntimeError("Reset detected") + if ( + s["step"] < self.last_step + or any(s.get("terminated", [])) + or any(s.get("truncated", [])) + ): + raise RuntimeError("Reset detected") + if time.time() - s["time"] > 3 or s["time"] > time.time() + 1: + raise RuntimeError("Stale observation") + if s["step"] > self.last_step: + self.last_step = s["step"] + # Pinned Lab 91a0906 uses Warp xyzw (BaseFrameTransformerData). + if s.get("quaternion_order") != "xyzw": + raise RuntimeError("Unqualified quaternion convention") + s = dict( + s, + tool_quaternion_xyzw=list(s["ee_quaternion"]), + object_quaternion_xyzw=list(s["object_quaternion"]), + ) + for key in [ + "ee", + "tool_quaternion_xyzw", + "object_quaternion_xyzw", + "object", + "joints", + "object_velocity", + "action", + ]: + if not all(math.isfinite(v) for v in s[key]): + raise RuntimeError("Nonfinite observation: " + key) + if abs(sum(v * v for v in s["tool_quaternion_xyzw"]) - 1) > 0.001: + raise RuntimeError("Invalid tool quaternion") + return s + time.sleep(0.01) + raise TimeoutError("Observation step did not advance") + + def neutral(self): + self.send( + [] + if self.name == "keyboard" + else {"lx": 0, "ly": 0, "rx": 0, "ry": 0, "hx": 0, "hy": 0, "x": 0} + ) + + def wait(self, seconds, *, deadline=None): + wall = time.monotonic() + max(10, seconds * 10) + if deadline is not None: + wall = min(wall, deadline) + start = self.observe(deadline=wall)["sim_time"] + while True: + if time.monotonic() >= wall: + raise TimeoutError("Simulation wait exceeded wall backstop") + if self.observe(deadline=wall)["sim_time"] - start >= seconds: + return + time.sleep(0.02) + + def move_tool(self, target, quaternion, *, timeout=60): + if ( + len(target) != 3 + or len(quaternion) != 4 + or not all(math.isfinite(v) for v in [*target, *quaternion]) + ): + raise ValueError("Invalid pose") + if abs(sum(v * v for v in quaternion) - 1) > 1e-5: + raise ValueError("Target quaternion must be unit xyzw") + desired = Rotation.from_quat(quaternion) + start = self.observe() + wall = time.monotonic() + timeout + # Reaching the pose while input is held is not settled convergence. + # Correct bounded release drift without relaxing pose tolerances or + # resetting the original wall/simulation budgets. All corrections still + # pass through the native keyboard/gamepad input path. + try: + for attempt in range(3): + while time.monotonic() < wall: + s = self.observe(deadline=wall) + error = [target[i] - s["ee"][i] for i in range(3)] + rotation = ( + desired * Rotation.from_quat(s["tool_quaternion_xyzw"]).inv() + ).as_rotvec() + if s["sim_time"] - start["sim_time"] > timeout: + raise TimeoutError("Movement simulation timeout") + if max(map(abs, error)) < 0.004 and max(map(abs, rotation)) < 0.035: + break + axis = max(range(3), key=lambda i: abs(rotation[i])) + if self.name == "keyboard": + command = [ + pair[int(error[i] < 0)] + for i, pair in enumerate( + [("w", "s"), ("a", "d"), ("q", "e")] + ) + if abs(error[i]) > 0.0025 + ] + if abs(rotation[axis]) > 0.035: + command.append( + [("z", "x"), ("t", "g"), ("c", "v")][axis][ + int(rotation[axis] < 0) + ] + ) + else: + v = [ + max(-0.3, min(0.3, e * 30)) if abs(e) > 0.002 else 0 + for e in error + ] + command = { + "ly": -v[0], + "lx": v[1], + "ry": -v[2], + "rx": 0, + "hx": 0, + "hy": 0, + "x": 0, + } + if abs(rotation[axis]) > 0.035: + sign = 1 if rotation[axis] > 0 else -1 + command.update( + [ + {"hx": -sign}, + {"hy": sign}, + {"rx": max(-0.5, min(0.5, rotation[2] * 5))}, + ][axis] + ) + self.send(command) + time.sleep(0.025 if self.name == "keyboard" else 0.08) + else: + raise TimeoutError("Movement wall timeout") + self.neutral() + self.wait(0.35, deadline=wall) + final = self.observe(deadline=wall) + if time.monotonic() >= wall: + raise TimeoutError("Movement wall timeout") + if final["sim_time"] - start["sim_time"] > timeout: + raise TimeoutError("Movement simulation timeout") + rot_error = ( + desired * Rotation.from_quat(final["tool_quaternion_xyzw"]).inv() + ).magnitude() + pos_error = max(abs(target[i] - final["ee"][i]) for i in range(3)) + if pos_error <= 0.009 and rot_error <= 0.07: + self.moves.append( + { + "target": list(target), + "position_error": pos_error, + "orientation_error": rot_error, + "step": final["step"], + "settle_attempts": attempt + 1, + } + ) + return final + _LOG.info( + "Target settling attempt %d/3: position error=%.6fm, orientation error=%.6frad", + attempt + 1, + pos_error, + rot_error, + ) + raise RuntimeError( + f"Target did not remain reached: position error={pos_error:.6f}m, orientation error={rot_error:.6f}rad" + ) + finally: + self.neutral() + + def set_gripper(self, *, closed): + if type(closed) is not bool: + raise TypeError("closed must be bool") + s = self.observe() + action_closed = s["action"][-1] < 0 + if closed != action_closed: + try: + self.send(["k"] if self.name == "keyboard" else {"x": 1}) + time.sleep(0.18) + finally: + self.neutral() + self.wait(1) + final = self.observe() + if (final["action"][-1] < 0) != closed: + raise RuntimeError("Native gripper command not confirmed; no blind retry") + aperture = sum(final["joints"][-2:]) + # Contact with the cube prevents full closure; lifting is a separate task guard. + if (not closed and min(final["joints"][-2:]) <= 0.035) or ( + closed and aperture >= 0.075 + ): + raise RuntimeError("Gripper physical response not confirmed") + return final diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py index ab3472ef92..a77fed462f 100644 --- a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/observe_record_demos.py @@ -20,7 +20,12 @@ import time from types import SimpleNamespace -from .task_hooks import check_driver_lease, evaluate_success, load_success_hook +from .task_hooks import ( + check_driver_lease, + evaluate_success, + install_reset_observer, + load_success_hook, +) from .observer_process import supervise, write_snapshot import numpy as np @@ -210,6 +215,10 @@ def create_environment_config(*args, **kwargs): def custom_success(env): import torch + # A reset increments episode_id before the throttled render publisher + # refreshes scene data. Never initialize a task callback from that + # mixed snapshot; read the native scene at this evaluation boundary. + publish(force=True) return torch.tensor( [evaluate_success(success_callback, state)], dtype=torch.bool, @@ -262,13 +271,16 @@ def measure_home(): values(ee.target_quat_source)[0][0] ).as_matrix() - # The task randomizes joints at reset. Capture the actual reset pose each - # time, before the native recorder forwards its RESET event to the device. + def record_reset(): + state["episode_id"] += 1 + + # Lab's step() resets terminated environments through _reset_idx, bypassing + # reset(). Observe that shared boundary without replacing native reset behavior. + install_reset_observer(environment, record_reset) original_reset = env.reset def reset(*args, **kwargs): result = original_reset(*args, **kwargs) - state["episode_id"] += 1 measure_home() return result @@ -287,7 +299,10 @@ def reset(*args, **kwargs): original_step = env.step def step(actions): + episode_before = state["episode_id"] result = original_step(actions) + if state["episode_id"] != episode_before: + measure_home() state["phase"] = "running" state["steps"] += 1 state["last_action"] = values(actions) diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/pick_place_logic.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/pick_place_logic.py new file mode 100644 index 0000000000..4ed1bf7386 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/pick_place_logic.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Scene selection is separate; execute the identical shared pick-and-place plan.""" + +import argparse +import json +import hashlib +from pathlib import Path +from .session import FrankaTaskSession +from .task_plan import Scene, execute, cube_top_down +from .xr_backend import XRBackend +from .pick_place_success import destination + + +def main(): + p = argparse.ArgumentParser() + p.add_argument("--observation-dir", required=True) + p.add_argument("--output", required=True) + a = p.parse_args() + with FrankaTaskSession( + observation_dir=a.observation_dir, output=a.output + ) as session: + initial = session.mark("initial") + goal = destination(initial) + q = cube_top_down(initial["cubes"]["cube_2"]["quaternion_world"][0]) + request = { + "backend": "xr", + "destination": goal.tolist(), + "orientation_xyzw": list(q), + "run_id": initial["run_id"], + "shared_plan_sha256": hashlib.sha256( + (Path(__file__).parent / "task_plan.py").read_bytes() + ).hexdigest(), + } + (Path(a.output) / "request.json").write_text(json.dumps(request, indent=2)) + backend = XRBackend(session) + result = execute(backend, session.mark, Scene(destination=tuple(goal))) + result["neutral_wait_until_step"] = backend.terminal_step + (Path(a.output) / "task-result.json").write_text(json.dumps(result, indent=2)) + session.wait_for_export(timeout=60) + + +if __name__ == "__main__": + main() diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/pick_place_success.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/pick_place_success.py new file mode 100644 index 0000000000..4dd8f03592 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/pick_place_success.py @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Pick-and-place destination and native success callback for the Franka scene.""" + +import numpy as np + +_history = {} + + +# One application-defined destination keeps driver and callback decisions identical. +DESTINATION_XY = (0.45, -0.20) + + +class DestinationUnavailable(RuntimeError): + """The configured placement region is unsuitable for this episode.""" + + +def destination(observation): + start = np.array(observation["cubes"]["cube_2"]["position_world"][0]) + goal = np.array([*DESTINATION_XY, start[2]]) + others = [ + np.array(value["position_world"][0]) + for name, value in observation["cubes"].items() + if name != "cube_2" + ] + if np.linalg.norm((goal - start)[:2]) <= 0.18 or any( + np.linalg.norm((goal - other)[:2]) <= 0.12 for other in others + ): + raise DestinationUnavailable( + "Example destination is occupied or too close; choose a different task destination" + ) + return goal + + +def is_success(observation): + episode = observation["episode_id"] + cube = np.array(observation["cubes"]["cube_2"]["position_world"][0]) + if _history.get("episode") != episode: + try: + goal = destination(observation) + except DestinationUnavailable: + goal = None + _history.update( + episode=episode, + start=cube.copy(), + goal=goal, + lifted=False, + ) + if _history["goal"] is None: + return False + _history["lifted"] |= bool(cube[2] - _history["start"][2] >= 0.08) + goal = _history["goal"] + ee = np.array(observation["ee_positions_world"][0][0]) + fingers = [ + value + for name, value in zip(observation["joint_names"], observation["joints"][0]) + if name.startswith("panda_finger_") + ] + return bool( + _history["lifted"] + and np.linalg.norm((cube - goal)[:2]) < 0.015 + and abs(cube[2] - goal[2]) < 0.008 + and np.linalg.norm(observation["cubes"]["cube_2"]["linear_velocity_world"][0]) + < 0.02 + and ee[2] - goal[2] >= 0.16 + and len(fingers) == 2 + and all(abs(f - 0.04) < 0.005 for f in fingers) + ) diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py index da52e1eac5..ddf7352d42 100644 --- a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/session.py @@ -14,9 +14,12 @@ from scipy.spatial.transform import Rotation from .browser_command import BrowserClient -from .xr_controller_bridge import XRControllerBridge, play, wait_steps +from .xr_controller_bridge import EpisodeCompleted, XRControllerBridge, play, wait_steps TOOL_OFFSET = np.array([0.0, 0.0, 0.1034]) +ORIENTATION_TOLERANCE = ( + 0.05 # Radians, measured tool orientation rather than action echo. +) def validate_observation(value): @@ -36,6 +39,9 @@ def validate_observation(value): or value.get("simulation_dt", 0) <= 0 ): raise ValueError("Invalid simulation step duration") + for name in ("episode_id", "steps", "exported_successful_episodes"): + if type(value.get(name)) is not int or value[name] < 0: + raise ValueError("Invalid observation counter: " + name) frame_count = None for name, width in (("ee_positions_world", 3), ("ee_quaternions_world", 4)): array = np.asarray(value[name]) @@ -51,6 +57,10 @@ def validate_observation(value): if frame_count is not None and array.shape[1] != frame_count: raise ValueError("Position and orientation target counts differ") frame_count = array.shape[1] + if name == "ee_quaternions_world" and not np.allclose( + np.linalg.norm(array, axis=-1), 1.0, atol=1e-3 + ): + raise ValueError("Measured tool quaternions must be unit xyzw values") for obj in value["cubes"].values(): for name, width in ( ("position_world", 3), @@ -92,8 +102,15 @@ def __init__( self.stop_observer = stop_observer self._trace = None self._entered = False + self.episode_id = None + self._last_step = None + self._starting = False + self._terminal = False + self._episode_error = None def state(self, max_age=5): + if self._episode_error is not None: + raise RuntimeError(self._episode_error) try: value = self.client.state(max_age) except (URLError, OSError): @@ -111,6 +128,25 @@ def state(self, max_age=5): raise RuntimeError("Feedback belongs to a different observation directory") if self.run_id is not None and value["run_id"] != self.run_id: raise RuntimeError("Observer restarted during the task") + if self.run_id is not None and not self._starting: + # Native export may reset the scene before the worker closes. It is + # terminal for this driver and must never rearm motion in the next episode. + self._terminal = self._terminal or value["exported_successful_episodes"] > 0 + if self.episode_id is None: + self.episode_id = value["episode_id"] + elif not self._terminal and ( + value["episode_id"] != self.episode_id + or (self._last_step is not None and value["steps"] < self._last_step) + ): + self._episode_error = "Episode reset during task; start a fresh session" + try: + self.client.command("stop") + except Exception as failure: + raise RuntimeError( + self._episode_error + "; input release failed" + ) from failure + raise RuntimeError(self._episode_error) + self._last_step = value["steps"] self._heartbeat() return value @@ -122,9 +158,17 @@ def _heartbeat(self): ) temporary.replace(self.observation_dir / "driver-heartbeat.json") - def command(self, *args, **kwargs): + def command(self, action, *args, **kwargs): + current = None + if action not in {"stop", "inspect"} and self.run_id is not None: + current = self.state() + if self._terminal or current.get("exported_successful_episodes", 0): + raise EpisodeCompleted("Episode already completed") self._heartbeat() - return self.client.command(*args, **kwargs) + receipt = self.client.command(action, *args, **kwargs) + if current is not None: + receipt["native_step_before_send"] = current["steps"] + return receipt def __enter__(self): if self._entered: @@ -146,6 +190,7 @@ def __enter__(self): with (self.observation_dir / "driver-owner.json").open("x") as owner: json.dump({"run_id": observation["run_id"], "pid": os.getpid()}, owner) self.run_id = observation["run_id"] + self._starting = True self.command("inspect") self.command( "head", quaternion=Rotation.from_euler("x", -0.55).as_quat().tolist() @@ -155,12 +200,15 @@ def __enter__(self): ) if not observation.get("control", {}).get("is_active"): play(client=self) + self._starting = False + self.state() self.controller.target_base( self.controller.target[:3, 3], self.controller.target[:3, :3] ) self.mark("started") return self except BaseException as error: + self._starting = False self.__exit__(type(error), error, error.__traceback__) raise @@ -209,7 +257,11 @@ def move_tool(self, position, quaternion, *, seconds=2): if current.get("exported_successful_episodes", 0): return current error = point - np.asarray(current["ee_positions_world"])[0, 0] - if np.linalg.norm(error) < 0.002: + angular_error = ( + Rotation.from_quat(q).inv() + * Rotation.from_quat(current["ee_quaternions_world"][0][0]) + ).magnitude() + if np.linalg.norm(error) < 0.002 and angular_error <= ORIENTATION_TOLERANCE: return current target = ( np.linalg.inv(self.controller.base_to_world) @ self.controller.target @@ -217,14 +269,21 @@ def move_tool(self, position, quaternion, *, seconds=2): self.controller.move_world( target[:3, 3] + np.clip(error, -0.01, 0.01), rotation, seconds=0.6 ) - if ( - np.linalg.norm(point - np.asarray(self.state()["ee_positions_world"])[0, 0]) - > 0.01 - ): + current = self.state() + if current.get("exported_successful_episodes", 0): + return current + position_error = np.linalg.norm( + point - np.asarray(current["ee_positions_world"])[0, 0] + ) + angular_error = ( + Rotation.from_quat(q).inv() + * Rotation.from_quat(current["ee_quaternions_world"][0][0]) + ).magnitude() + if position_error > 0.01 or angular_error > ORIENTATION_TOLERANCE: raise RuntimeError( - "Tool did not converge within 0.01 m of its requested target" + "Tool did not converge within 0.01 m and 0.05 radians of its requested pose" ) - return self.state() + return current def set_gripper(self, *, closed): if type(closed) is not bool: diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_hooks.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_hooks.py index d7bfa8dcb7..ab9bd6c52f 100644 --- a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_hooks.py +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_hooks.py @@ -12,6 +12,26 @@ from pathlib import Path +def install_reset_observer(environment, on_reset): + """Observe both explicit resets and Lab's same-step automatic resets.""" + original = environment._reset_idx + + def reset_indices(env_ids, *args, **kwargs): + # Mark the boundary before native reset/rendering can publish a new scene. + # Explicit resets use slices; automatic resets supply index tensors. + if env_ids is None: + count = environment.num_envs + elif isinstance(env_ids, slice): + count = len(range(environment.num_envs)[env_ids]) + else: + count = len(env_ids) + if count: + on_reset() + return original(env_ids, *args, **kwargs) + + environment._reset_idx = reset_indices + + def load_success_hook(specification): module, separator, name = specification.partition(":") if not separator or not module or not name.isidentifier(): diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_plan.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_plan.py new file mode 100644 index 0000000000..c27fdf7376 --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/task_plan.py @@ -0,0 +1,145 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""One task plan for native keyboard/gamepad and Synthetic XR backends. + +No key names, axes, controller poses, SDK action writes or QA verdicts belong here. +World metres, tool-frame targets, xyzw unit quaternion; scene bounds are explicit. +""" + +from dataclasses import dataclass +import math +from typing import Protocol + + +class TaskBackend(Protocol): + """Tool targets are world metres/Z-up and unit xyzw; input stays in adapters.""" + + name: str + + def observe(self) -> dict: + """Return fresh tool/object poses, joints and object velocity.""" + ... + + def move_tool(self, position, quaternion): ... + + def set_gripper(self, *, closed: bool): ... + + def wait(self, seconds: float): ... + + def neutral(self): + """Stop motion/release button edges without opening a held gripper.""" + ... + + +@dataclass(frozen=True) +class Scene: + orientation: tuple | None = None + clearance: float = 0.17 + contact_offset: float = 0.003 + retreat: float = 0.18 + minimum_lift: float = 0.10 + destination: tuple | None = None + + +def cube_top_down(quaternion): + """Square cube symmetry: use the nearest equivalent top-down grasp yaw.""" + x, y, z, w = quaternion + yaw = math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)) + yaw = (yaw + math.pi / 4) % (math.pi / 2) - math.pi / 4 + return (math.cos(yaw / 2), math.sin(yaw / 2), 0.0, 0.0) + + +def execute(backend: TaskBackend, phase, scene: Scene | None = None): + # Construct per-call configuration rather than using a shared default object. + scene = Scene() if scene is None else scene + checkpoints = [] + failure = None + + def snapshot(label): + s = backend.observe() + checkpoints.append({"label": label, "state": s}) + return s + + def move(label, position): + phase(label) + backend.move_tool(position, orientation) + return snapshot(label) + + def grip(label, closed): + phase(label) + backend.set_gripper(closed=closed) + return snapshot(label) + + try: + initial = snapshot("initial") + orientation = scene.orientation or cube_top_down( + initial["object_quaternion_xyzw"] + ) + cube = list(initial["object"]) + destination = list( + scene.destination or (cube[0], -0.15 if cube[1] > 0 else 0.15, cube[2]) + ) + if scene.destination is None and abs(destination[1] - cube[1]) < 0.15: + destination[1] = cube[1] - 0.2 + phase("align gripper") + backend.move_tool(initial["ee"], orientation) + backend.set_gripper(closed=False) + move("approach above cube", [cube[0], cube[1], cube[2] + scene.clearance]) + move("lower open gripper", [cube[0], cube[1], cube[2] + scene.contact_offset]) + grip("close gripper", True) + lifted = move("lift cube", [cube[0], cube[1], cube[2] + scene.clearance]) + if lifted["object"][2] - cube[2] < scene.minimum_lift: + raise RuntimeError("Task guard: cube did not lift") + offset = [lifted["object"][i] - lifted["ee"][i] for i in range(3)] + carry = move( + "carry to destination", + [ + destination[0] - offset[0], + destination[1] - offset[1], + cube[2] + scene.clearance, + ], + ) + if carry["object"][2] - cube[2] < scene.minimum_lift: + raise RuntimeError("Task guard: cube dropped during carry") + move( + "lower cube onto table", + [ + destination[i] - offset[i] + (scene.contact_offset if i == 2 else 0) + for i in range(3) + ], + ) + grip("release cube", False) + move( + "retreat with open gripper", + [ + destination[0] - offset[0], + destination[1] - offset[1], + cube[2] + scene.retreat, + ], + ) + phase("hands off: stable placement") + backend.neutral() + backend.wait(3) + final = snapshot("stable placement") + return { + "task_completed": True, + "initial_object": cube, + "destination": destination, + "placement_error_m": math.dist(final["object"][:2], destination[:2]), + "checkpoints": checkpoints, + "backend": backend.name, + } + except BaseException as error: + failure = error + raise + finally: + # Stop motion/release button edges. Backend retains explicit gripper state. + try: + backend.neutral() + except BaseException as cleanup: + if failure is None: + raise + failure.cleanup_error = repr(cleanup) + if hasattr(failure, "add_note"): + failure.add_note("Cleanup also failed: " + repr(cleanup)) diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_backend.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_backend.py new file mode 100644 index 0000000000..4363ff091b --- /dev/null +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_backend.py @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Same task contract, through the existing public Synthetic XR session API.""" + +import math +from .xr_controller_bridge import EpisodeCompleted + + +class XRBackend: + name = "xr" + + def __init__(self, session, object_name="cube_2"): + self.session = session + self.object_name = object_name + self.episode = None + self.last_step = -1 + self.trigger = 0.0 + self.terminal_step = None + + def observe(self): + s = self.session.observe() + terminal = s.get("phase") == "completed" or s.get( + "exported_successful_episodes", 0 + ) + if terminal and ( + not s.get("exported_successful_episodes", 0) + or self.terminal_step is None + or s["steps"] < self.terminal_step + ): + raise RuntimeError("XR exported before the shared task finished") + if self.episode is None: + self.episode = s["episode_id"] + if s["episode_id"] != self.episode or s["steps"] < self.last_step: + raise RuntimeError("XR episode changed during task") + self.last_step = s["steps"] + obj = s["cubes"][self.object_name] + return dict( + run_id=s["run_id"], + step=s["steps"], + sim_time=s["steps"] * s["simulation_dt"], + ee=s["ee_positions_world"][0][0], + tool_quaternion_xyzw=s["ee_quaternions_world"][0][0], + object=obj["position_world"][0], + object_velocity=obj["linear_velocity_world"][0], + object_quaternion_xyzw=obj["quaternion_world"][0], + joints=s["joints"][0], + ) + + def move_tool(self, target, quaternion): + self.terminal_step = None + # The session owns measured position/orientation convergence and its limits. + self.session.move_tool(target, quaternion, seconds=3) + return self.observe() + + def set_gripper(self, *, closed): + self.terminal_step = None + self.session.set_gripper(closed=closed) + self.trigger = float(closed) + self.session.wait(1) + s = self.observe() + if (not closed and min(s["joints"][-2:]) <= 0.035) or ( + closed and sum(s["joints"][-2:]) >= 0.075 + ): + raise RuntimeError("XR gripper physical response missing") + return s + + def wait(self, seconds): + if not math.isfinite(seconds) or not 0 < seconds <= 120: + raise ValueError("Wait duration must be in (0, 120] simulation seconds") + self.terminal_step = None + current = self.session.observe() + self.observe() # Reject export/reset before beginning the neutral wait. + self.terminal_step = current["steps"] + math.ceil( + seconds / current["simulation_dt"] + ) + self.session.wait(seconds) + self.observe() # Native export may end wait_steps early; still require the duration. + + def neutral(self): + # Clutch release retains the trigger state; do not drop a carried object. + try: + self.session.command( + "pose", hand="right", buttons={"squeeze": 0, "trigger": self.trigger} + ) + except EpisodeCompleted: + # Export can race with cleanup; stop is allowed after terminal export. + self.session.command("stop") diff --git a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py index d36df9e39b..85f4b01c45 100644 --- a/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py +++ b/examples/synthetic_xr/python/isaaccapture_examples/synthetic_xr/franka/xr_controller_bridge.py @@ -13,6 +13,10 @@ from . import browser_command +class EpisodeCompleted(RuntimeError): + """Native export ended a command while the task was in progress.""" + + def transform(position, quaternion): result = np.eye(4) result[:3, 3] = position @@ -20,6 +24,32 @@ def transform(position, quaternion): return result +def validate_pose(position, rotation): + p, rot = np.asarray(position, dtype=float), np.asarray(rotation, dtype=float) + if ( + p.shape != (3,) + or rot.shape != (3, 3) + or not np.isfinite(p).all() + or not np.isfinite(rot).all() + ): + raise ValueError("Invalid target pose") + if not np.allclose(rot.T @ rot, np.eye(3), atol=1e-6) or not np.isclose( + np.linalg.det(rot), 1.0, atol=1e-6 + ): + raise ValueError("Target rotation must be orthonormal with determinant +1") + return p, rot + + +def validate_target(position, rotation, trigger): + p, rot = validate_pose(position, rotation) + if np.linalg.norm(p) > 1.5: + raise ValueError("Target outside declared Franka test workspace") + trigger = float(trigger) + if not 0 <= trigger <= 1: + raise ValueError("Invalid trigger") + return p, rot, trigger + + def wait_steps(count=3, timeout=20, client=browser_command): first = client.state()["steps"] deadline = time.monotonic() + timeout @@ -73,23 +103,9 @@ def __init__(self, client=browser_command, acknowledgement_path=None): self.target_base(self.target[:3, 3], self.target[:3, :3], wait_receipt=False) def target_base(self, position, rotation, trigger=None, wait_receipt=True): - p, rot = np.asarray(position), np.asarray(rotation) - if ( - p.shape != (3,) - or rot.shape != (3, 3) - or not np.isfinite(p).all() - or not np.isfinite(rot).all() - ): - raise ValueError("Invalid target pose") - if not np.allclose(rot.T @ rot, np.eye(3), atol=1e-6) or not np.isclose( - np.linalg.det(rot), 1.0, atol=1e-6 - ): - raise ValueError("Target rotation must be orthonormal with determinant +1") - if np.linalg.norm(p) > 1.5: - raise ValueError("Target outside declared Franka test workspace") - self.trigger = self.trigger if trigger is None else float(trigger) - if not 0 <= self.trigger <= 1: - raise ValueError("Invalid trigger") + p, rot, next_trigger = validate_target( + position, rotation, self.trigger if trigger is None else trigger + ) base_grip = np.eye(4) base_grip[:3, 3] = p base_grip[:3, :3] = rot @ self.offset.T @@ -98,12 +114,23 @@ def target_base(self, position, rotation, trigger=None, wait_receipt=True): @ base_grip @ np.linalg.inv(self.aim_to_grip) ) - result = self.client.command( - "pose", - position=aim[:3, 3].tolist(), - quaternion=Rotation.from_matrix(aim[:3, :3]).as_quat().tolist(), - buttons={"squeeze": 1, "trigger": self.trigger}, + before = self.client.state() + if before.get("exported_successful_episodes", 0): + return None + step_at_send = before["steps"] + try: + result = self.client.command( + "pose", + position=aim[:3, 3].tolist(), + quaternion=Rotation.from_matrix(aim[:3, :3]).as_quat().tolist(), + buttons={"squeeze": 1, "trigger": next_trigger}, + ) + except EpisodeCompleted: + return None + step_at_send = max( + step_at_send, result.get("native_step_before_send", step_at_send) ) + self.trigger = next_trigger self.target = transform(p, Rotation.from_matrix(rot).as_quat()) if not wait_receipt: return result @@ -116,7 +143,8 @@ def target_base(self, position, rotation, trigger=None, wait_receipt=True): return result action = np.asarray(observed["last_action"])[0] if ( - observed["timestamp"] >= result["timestamp"] + observed["steps"] > step_at_send + and observed["timestamp"] >= result["timestamp"] and np.linalg.norm(action[:3] - p) < 1e-4 and abs(float(action[3:7] @ wanted_q)) > 0.99999 and abs(action[7] - gripper) < 0.001 @@ -127,6 +155,7 @@ def target_base(self, position, rotation, trigger=None, wait_receipt=True): "timestamp": time.time(), "browser_command_id": result["id"], "browser_ack_timestamp": result["timestamp"], + "native_step_before_send": step_at_send, "native_step": observed["steps"], "native_timestamp": observed["timestamp"], "raw_trigger": self.trigger, @@ -151,10 +180,16 @@ def move_world(self, position, rotation=None, trigger=None, seconds=2): raise ValueError("Movement duration must be in (0, 120] seconds") current_world = np.linalg.inv(self.base_to_world) @ self.target rotation = current_world[:3, :3] if rotation is None else rotation + position, rotation = validate_pose(position, rotation) target = self.base_to_world @ transform( position, Rotation.from_matrix(rotation).as_quat() ) origin = self.target.copy() + # The base-frame workspace is convex: admit both endpoints before any input. + validate_target( + target[:3, 3], target[:3, :3], self.trigger if trigger is None else trigger + ) + validate_target(origin[:3, 3], origin[:3, :3], self.trigger) steps = max(2, round(seconds * 5)) orientations = Slerp( [0, 1], Rotation.from_matrix([origin[:3, :3], target[:3, :3]]) diff --git a/examples/synthetic_xr/tests/test_input_backends.py b/examples/synthetic_xr/tests/test_input_backends.py new file mode 100644 index 0000000000..f4b2358b43 --- /dev/null +++ b/examples/synthetic_xr/tests/test_input_backends.py @@ -0,0 +1,178 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Negative controls for the input adapters and independent outcome oracle.""" + +import time +import unittest +from unittest.mock import patch +from isaaccapture_examples.synthetic_xr.franka.native_backend import NativeBackend +from isaaccapture_examples.synthetic_xr.franka.task_plan import execute + + +def state(): + return dict( + run_id="test", + step=1, + time=time.time(), + sim_time=0.02, + quaternion_order="xyzw", + episode_id=0, + tool_frame={ + "sensor": "ee_frame", + "index": 0, + "body_name": "panda_hand", + "offset_position": [0.0, 0.0, 0.1034], + }, + object_quaternion=[0, 0, 0, 1], + ee=[0.4, 0, 0.3], + ee_quaternion=[1, 0, 0, 0], + object=[0.4, 0, 0.025], + joints=[0] * 7 + [0.04, 0.04], + object_velocity=[0, 0, 0], + action=[0] * 6 + [1], + terminated=[False], + truncated=[False], + ) + + +class Contracts(unittest.TestCase): + def backend(self, s, **kwargs): + sent = [] + b = NativeBackend( + "keyboard", lambda: s, lambda x: sent.append(x), "test", **kwargs + ) + return b, sent + + def test_reject_foreign_run(self): + s = state() + s["run_id"] = "other" + b, _ = self.backend(s) + with self.assertRaisesRegex(RuntimeError, "Foreign"): + b.observe() + + def test_reject_stale(self): + s = state() + s["time"] -= 4 + b, _ = self.backend(s) + with self.assertRaisesRegex(RuntimeError, "Stale"): + b.observe() + + def test_reject_reset(self): + for flag in ["terminated", "truncated"]: + s = state() + s[flag] = [True] + b, _ = self.backend(s) + with self.assertRaisesRegex(RuntimeError, "Reset"): + b.observe() + + def test_reject_step_regression(self): + s = state() + b, _ = self.backend(s) + b.observe() + s["step"] = 0 + with self.assertRaisesRegex(RuntimeError, "Reset"): + b.observe() + + def test_reject_quaternion_order(self): + s = state() + s["quaternion_order"] = "wxyz" + b, _ = self.backend(s) + with self.assertRaisesRegex(RuntimeError, "convention"): + b.observe() + + def test_xyzw_is_not_reordered(self): + b, _ = self.backend(state()) + self.assertEqual(b.observe()["tool_quaternion_xyzw"], [1, 0, 0, 0]) + + def test_cancel(self): + b, _ = self.backend(state(), cancelled=lambda: True) + with self.assertRaisesRegex(RuntimeError, "Cancelled"): + b.observe() + + def test_expired_observation_deadline_times_out(self): + b, _ = self.backend(state()) + b.observe() + with patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.monotonic", + side_effect=[0, 4], + ): + with self.assertRaisesRegex(TimeoutError, "advance"): + b.observe() + + def test_invalid_target_emits_nothing(self): + b, sent = self.backend(state()) + with self.assertRaises(ValueError): + b.move_tool([float("nan"), 0, 0], [1, 0, 0, 0]) + self.assertEqual(sent, []) + + def test_gripper_open_is_idempotent(self): + s = state() + b, sent = self.backend(s) + with patch.object(b, "observe", return_value=s), patch.object(b, "wait"): + b.set_gripper(closed=False) + b.set_gripper(closed=False) + self.assertEqual(sent, []) + + def test_missing_gripper_ack_no_retry_and_release(self): + s = state() + b, sent = self.backend(s) + with ( + patch.object(b, "observe", return_value=s), + patch.object(b, "wait"), + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.sleep" + ), + ): + with self.assertRaisesRegex(RuntimeError, "no blind retry"): + b.set_gripper(closed=True) + self.assertEqual(sent, [["k"], []]) + + def test_task_failure_stops_input(self): + b, sent = self.backend(state()) + with patch.object(b, "observe", side_effect=RuntimeError("stale")): + with self.assertRaises(RuntimeError): + execute(b, lambda p: None) + self.assertEqual(sent, [[]]) + + def test_unreachable_target_times_out_and_neutralizes(self): + b, sent = self.backend(state()) + with ( + patch.object(b, "observe", return_value=state()), + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.monotonic", + side_effect=[0, 2], + ), + ): + with self.assertRaisesRegex(TimeoutError, "wall timeout"): + b.move_tool([0.6, 0.2, 0.4], [1, 0, 0, 0], timeout=1) + self.assertEqual(sent, [[]]) + + def test_cleanup_preserves_primary_failure(self): + b, _ = self.backend(state()) + with ( + patch.object(b, "observe", side_effect=RuntimeError("primary")), + patch.object(b, "neutral", side_effect=RuntimeError("cleanup")), + ): + with self.assertRaisesRegex(RuntimeError, "primary") as caught: + execute(b, lambda p: None) + self.assertIn("cleanup", caught.exception.cleanup_error) + + def test_wrong_tool_point_rejected(self): + s = state() + s["tool_frame"]["offset_position"] = [0, 0, 0] + b, _ = self.backend(s) + with self.assertRaisesRegex(RuntimeError, "tool frame"): + b.observe() + + def test_skipped_reset_detected(self): + s = state() + b, _ = self.backend(s) + b.observe() + s.update(step=10, episode_id=1) + with self.assertRaisesRegex(RuntimeError, "Reset"): + b.observe() + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/examples/synthetic_xr/tests/test_native_settling.py b/examples/synthetic_xr/tests/test_native_settling.py new file mode 100644 index 0000000000..15b27ef20e --- /dev/null +++ b/examples/synthetic_xr/tests/test_native_settling.py @@ -0,0 +1,212 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Regression controls for release drift without weaker pose acceptance.""" + +import unittest +from unittest.mock import Mock, patch +from test_input_backends import state +from isaaccapture_examples.synthetic_xr.franka.native_backend import NativeBackend + + +def observed(x=0.0, q=(1.0, 0.0, 0.0, 0.0), sim=0.02): + return { + "ee": [x, 0.0, 0.0], + "tool_quaternion_xyzw": list(q), + "step": 10, + "sim_time": sim, + } + + +class Settling(unittest.TestCase): + def backend(self, kind="keyboard"): + sent = [] + return NativeBackend(kind, lambda: None, sent.append, "test"), sent + + def test_corrects_release_drift_using_input_then_accepts_settled_pose(self): + for kind in ["keyboard", "gamepad"]: + with self.subTest(kind=kind): + b, sent = self.backend(kind) + samples = [ + observed(), + observed(), + observed(0.012), + observed(0.012), + observed(), + observed(0.002), + ] + with ( + patch.object(b, "observe", side_effect=samples), + patch.object(b, "wait"), + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.sleep" + ), + ): + final = b.move_tool([0, 0, 0], [1, 0, 0, 0]) + self.assertEqual(final["ee"][0], 0.002) + self.assertEqual(b.moves[0]["settle_attempts"], 2) + if kind == "keyboard": + self.assertIn(["s"], sent) + self.assertEqual(sent[-1], []) + else: + self.assertTrue(any(x.get("ly", 0) > 0 for x in sent)) + self.assertEqual( + set(sent[-1]), {"lx", "ly", "rx", "ry", "hx", "hy", "x"} + ) + self.assertTrue(all(v == 0 for v in sent[-1].values())) + + def test_three_failed_settles_remain_failure(self): + b, sent = self.backend() + samples = [ + observed(), + observed(), + observed(0.0091), + observed(0.012), + observed(), + observed(0.0091), + observed(0.012), + observed(), + observed(0.0091), + ] + with ( + patch.object(b, "observe", side_effect=samples), + patch.object(b, "wait") as wait, + ): + with self.assertRaisesRegex(RuntimeError, "did not remain reached"): + b.move_tool([0, 0, 0], [1, 0, 0, 0]) + self.assertEqual(wait.call_count, 3) + self.assertEqual(sum(bool(x) for x in sent), 2) + self.assertEqual(b.moves, []) + self.assertEqual(sent[-1], []) + + def test_wrong_settled_orientation_remains_failure(self): + b, sent = self.backend() + samples = [ + observed(), + observed(), + observed(q=(0, 0, 0, 1)), + observed(q=(0, 0, 0, 1)), + observed(), + observed(q=(0, 0, 0, 1)), + observed(q=(0, 0, 0, 1)), + observed(), + observed(q=(0, 0, 0, 1)), + ] + with ( + patch.object(b, "observe", side_effect=samples), + patch.object(b, "wait") as wait, + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.sleep" + ), + ): + with self.assertRaisesRegex(RuntimeError, "did not remain reached"): + b.move_tool([0, 0, 0], [1, 0, 0, 0]) + self.assertEqual(wait.call_count, 3) + self.assertEqual(sum(bool(x) for x in sent), 2) + self.assertEqual(b.moves, []) + self.assertEqual(sent[-1], []) + + def test_correction_keeps_original_wall_deadline(self): + b, sent = self.backend() + with ( + patch.object( + b, "observe", side_effect=[observed(), observed(), observed(0.02)] + ), + patch.object(b, "wait"), + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.monotonic", + side_effect=[0, 0.1, 0.2, 1.1], + ), + ): + with self.assertRaisesRegex(TimeoutError, "wall timeout"): + b.move_tool([0, 0, 0], [1, 0, 0, 0], timeout=1) + self.assertEqual(sent[-1], []) + + def test_reset_during_correction_releases_input(self): + b, sent = self.backend() + with ( + patch.object( + b, + "observe", + side_effect=[ + observed(), + observed(), + observed(0.02), + RuntimeError("Reset detected"), + ], + ), + patch.object(b, "wait"), + ): + with self.assertRaisesRegex(RuntimeError, "Reset detected"): + b.move_tool([0, 0, 0], [1, 0, 0, 0]) + self.assertEqual(b.moves, []) + self.assertEqual(sent[-1], []) + + def test_correction_keeps_original_simulation_deadline(self): + b, sent = self.backend() + with ( + patch.object( + b, + "observe", + side_effect=[observed(), observed(), observed(0.02), observed(sim=2)], + ), + patch.object(b, "wait"), + ): + with self.assertRaisesRegex(TimeoutError, "simulation timeout"): + b.move_tool([0, 0, 0], [1, 0, 0, 0], timeout=1) + self.assertEqual(sent[-1], []) + + def test_settle_wait_uses_move_deadline(self): + b, _ = self.backend() + with ( + patch.object( + b, "observe", side_effect=[observed(), observed(), observed()] + ), + patch.object(b, "wait") as wait, + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.monotonic", + return_value=0, + ), + ): + b.move_tool([0, 0, 0], [1, 0, 0, 0], timeout=1) + wait.assert_called_once_with(0.35, deadline=1) + + def test_wait_itself_stops_at_parent_deadline(self): + b, _ = self.backend() + with ( + patch.object(b, "observe", return_value=observed()) as observe, + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.monotonic", + side_effect=[0, 0.1, 0.3], + ), + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.sleep" + ), + ): + with self.assertRaisesRegex(TimeoutError, "wall backstop"): + b.wait(0.35, deadline=0.25) + self.assertTrue( + all(c.kwargs["deadline"] == 0.25 for c in observe.call_args_list) + ) + + def test_frozen_observation_uses_parent_deadline(self): + b, _ = self.backend() + snapshot = state() + b.last_step = snapshot["step"] + b.read = Mock(return_value=snapshot) + with ( + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.monotonic", + side_effect=[0, 0.1, 0.2, 0.3], + ), + patch( + "isaaccapture_examples.synthetic_xr.franka.native_backend.time.sleep" + ), + ): + with self.assertRaisesRegex(TimeoutError, "did not advance"): + b.observe(deadline=0.25) + self.assertEqual(b.read.call_count, 2) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/examples/synthetic_xr/tests/test_shared_task.py b/examples/synthetic_xr/tests/test_shared_task.py new file mode 100644 index 0000000000..a51c40e797 --- /dev/null +++ b/examples/synthetic_xr/tests/test_shared_task.py @@ -0,0 +1,335 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Task guards and adapter contracts without native runtime or injected joints.""" + +import ast +import copy +from pathlib import Path +from types import SimpleNamespace +import unittest +from unittest.mock import Mock, patch + +from isaaccapture_examples.synthetic_xr.franka import pick_place_success +from isaaccapture_examples.synthetic_xr.franka.task_plan import execute +from isaaccapture_examples.synthetic_xr.franka.xr_backend import XRBackend + + +class SharedTaskTests(unittest.TestCase): + def test_lift_and_carry_failures_release_input(self): + initial = { + "object": [0.5, 0.1, 0.02], + "object_quaternion_xyzw": [0, 0, 0, 1], + "ee": [0.5, 0.1, 0.3], + } + lifted = dict(initial, object=[0.5, 0.1, 0.19], ee=[0.5, 0.1, 0.19]) + for snapshots, error in [ + ([initial] * 5, "did not lift"), + ([initial] * 4 + [lifted, initial], "dropped during carry"), + ]: + with self.subTest(error=error): + backend = Mock(name="backend") + backend.observe.side_effect = snapshots + with self.assertRaisesRegex(RuntimeError, error): + execute(backend, lambda _: None) + backend.neutral.assert_called_once() + backend.wait.assert_not_called() + self.assertEqual(backend.set_gripper.call_args.kwargs, {"closed": True}) + + def test_success_callback_requires_lift_release_and_retreat(self): + initial = dict( + episode_id=7, + cubes={ + "cube_2": { + "position_world": [[0.6, 0, 0.02]], + "linear_velocity_world": [[0, 0, 0]], + } + }, + ee_positions_world=[[[0.6, 0, 0.02]]], + joint_names=["panda_finger_joint1", "panda_finger_joint2"], + joints=[[0.04, 0.04]], + ) + pick_place_success._history.clear() + self.assertFalse(pick_place_success.is_success(initial)) + goal = pick_place_success.destination(initial) + placed = copy.deepcopy(initial) + placed["cubes"]["cube_2"]["position_world"] = [goal.tolist()] + placed["ee_positions_world"] = [[[goal[0], goal[1], 0.22]]] + self.assertFalse( + pick_place_success.is_success(placed), "placement without lift cannot pass" + ) + lift = copy.deepcopy(initial) + lift["cubes"]["cube_2"]["position_world"][0][2] = 0.20 + self.assertFalse(pick_place_success.is_success(lift)) + closed = copy.deepcopy(placed) + closed["joints"] = [[0.01, 0.01]] + self.assertFalse(pick_place_success.is_success(closed)) + self.assertTrue(pick_place_success.is_success(placed)) + reset = copy.deepcopy(initial) + reset["episode_id"] += 1 + self.assertFalse( + pick_place_success.is_success(reset), + "reset cannot retain the lift history", + ) + + def test_xr_neutral_releases_clutch_without_dropping_held_cube(self): + session = Mock() + adapter = XRBackend(session) + adapter.trigger = 1.0 + adapter.neutral() + session.command.assert_called_once_with( + "pose", hand="right", buttons={"squeeze": 0, "trigger": 1.0} + ) + + def test_xr_adapter_rejects_reset_and_early_export(self): + observation = dict( + run_id="run", + episode_id=0, + steps=1, + simulation_dt=0.02, + cubes={ + "cube_2": { + "position_world": [[0.5, 0, 0.02]], + "quaternion_world": [[0, 0, 0, 1]], + "linear_velocity_world": [[0, 0, 0]], + } + }, + ee_positions_world=[[[0.5, 0, 0.2]]], + ee_quaternions_world=[[[1, 0, 0, 0]]], + joints=[[0] * 7 + [0.04, 0.04]], + ) + adapter = XRBackend(SimpleNamespace(observe=lambda: observation)) + adapter.observe() + observation.update(episode_id=1, steps=2) + with self.assertRaisesRegex(RuntimeError, "episode changed"): + adapter.observe() + observation["exported_successful_episodes"] = 1 + with self.assertRaisesRegex(RuntimeError, "before the shared task finished"): + adapter.observe() + + +class XRExportRaceTests(unittest.TestCase): + class Session: + # Pinned record_demos with --num_demos 1 exits before handle_reset. + def __init__(self, settle_steps=150): + self.steps = 1 + self.settle_steps = settle_steps + self.exported = False + self.closed = False + self.ee = [0.5, 0.1, 0.3] + self.cube = [0.5, 0.1, 0.02] + self.commands = [] + + def observe(self): + return dict( + joint_names=[f"panda_joint{i}" for i in range(7)] + + ["panda_finger_joint1", "panda_finger_joint2"], + run_id="run", + episode_id=0, + steps=self.steps, + simulation_dt=0.02, + exported_successful_episodes=int(self.exported), + phase="completed" if self.exported else "running", + cubes={ + "cube_2": { + "position_world": [self.cube[:]], + "quaternion_world": [[0, 0, 0, 1]], + "linear_velocity_world": [[0, 0, 0]], + } + }, + ee_positions_world=[[self.ee[:]]], + ee_quaternions_world=[[[1, 0, 0, 0]]], + joints=[[0] * 7 + ([0.02, 0.02] if self.closed else [0.04, 0.04])], + ) + + def move_tool(self, position, quaternion, *, seconds): + self.steps += 150 + self.ee = list(position) + if self.closed: + self.cube = [position[0], position[1], position[2] - 0.003] + + def set_gripper(self, *, closed): + self.closed = closed + if not closed: + self.cube[2] = 0.02 + + def wait(self, seconds): + if seconds == 3: + self.steps += self.settle_steps + self.exported = True + else: + self.steps += 50 + + def command(self, action, **kwargs): + from isaaccapture_examples.synthetic_xr.franka.xr_controller_bridge import ( + EpisodeCompleted, + ) + + if action == "pose" and self.exported: + raise EpisodeCompleted("Native export raced cleanup") + self.commands.append((action, kwargs)) + + def test_complete_task_accepts_export_after_full_neutral_wait(self): + session = self.Session() + result = execute(XRBackend(session), lambda _: None) + self.assertTrue(result["task_completed"]) + self.assertEqual(session.commands[-1], ("stop", {})) + self.assertTrue(session.exported) + + def test_export_one_step_before_settling_finishes_is_failure(self): + session = self.Session(settle_steps=149) + with self.assertRaisesRegex(RuntimeError, "before the shared task finished"): + execute(XRBackend(session), lambda _: None) + self.assertEqual(session.commands[-1], ("stop", {})) + + def test_cleanup_failure_is_not_hidden_after_successful_export(self): + session = self.Session() + original = session.command + + def command(action, **kwargs): + if action == "stop": + raise OSError("stop failed") + return original(action, **kwargs) + + session.command = command + with self.assertRaisesRegex(OSError, "stop failed"): + execute(XRBackend(session), lambda _: None) + + def test_destination_choice_cannot_flip_with_scene_settling(self): + observation = self.Session().observe() + goal = pick_place_success.destination(observation) + observation["cubes"]["cube_2"]["position_world"][0][1] += 0.001 + moved = pick_place_success.destination(observation) + self.assertEqual(list(goal[:2]), list(moved[:2])) + observation["cubes"]["cube_1"] = {"position_world": [goal.tolist()]} + with self.assertRaisesRegex(RuntimeError, "occupied"): + pick_place_success.destination(observation) + + def test_callback_returns_false_for_blocked_new_episode(self): + observation = self.Session().observe() + pick_place_success._history.clear() + self.assertFalse(pick_place_success.is_success(observation)) + observation["episode_id"] += 1 + observation["cubes"]["cube_1"] = {"position_world": [[0.45, -0.20, 0.02]]} + self.assertFalse(pick_place_success.is_success(observation)) + observation["cubes"]["cube_2"]["position_world"][0][2] += 0.2 + self.assertFalse(pick_place_success.is_success(observation)) + + def test_native_wait_returns_export_before_requested_step_count(self): + from isaaccapture_examples.synthetic_xr.franka.xr_controller_bridge import ( + wait_steps, + ) + + terminal = {"steps": 149, "exported_successful_episodes": 1} + client = SimpleNamespace(state=Mock(side_effect=[{"steps": 1}, terminal])) + self.assertIs(wait_steps(count=150, client=client), terminal) + + +class XRSuccessWindowTests(unittest.TestCase): + class TimedSession(XRExportRaceTests.Session): + def __init__(self, success_steps): + super().__init__() + self.success_steps = success_steps + self.success_onset = None + self.released = False + self.latency_applied = False + + def set_gripper(self, *, closed): + self.released = self.released or (self.closed and not closed) + super().set_gripper(closed=closed) + + def move_tool(self, position, quaternion, *, seconds): + super().move_tool(position, quaternion, seconds=seconds) + if self.released: + # Conservative onset before the end of retreat, plus later transport latency. + self.success_onset = self.steps - 65 + + def command(self, action, **kwargs): + if self.success_onset is not None and not self.latency_applied: + self.steps += 45 + self.latency_applied = True + return super().command(action, **kwargs) + + def wait(self, seconds): + if self.success_onset is None: + return super().wait(seconds) + export_at = self.success_onset + self.success_steps + self.steps = min(self.steps + round(seconds / 0.02), export_at) + self.exported = self.steps == export_at + + def test_300_step_window_preserves_full_wait_despite_early_onset_and_latency(self): + session = self.TimedSession(300) + self.assertTrue(execute(XRBackend(session), lambda _: None)["task_completed"]) + self.assertFalse(session.exported) + session.wait(1) + self.assertTrue(session.exported) + + def test_200_step_window_can_export_too_early_and_must_fail(self): + session = self.TimedSession(200) + with self.assertRaisesRegex(RuntimeError, "before the shared task finished"): + execute(XRBackend(session), lambda _: None) + + def test_success_requires_near_final_vertical_retreat(self): + observation = XRExportRaceTests.Session().observe() + pick_place_success._history.clear() + self.assertFalse(pick_place_success.is_success(observation)) + observation["cubes"]["cube_2"]["position_world"][0][2] = 0.19 + self.assertFalse(pick_place_success.is_success(observation)) + goal = [0.45, -0.20, 0.02] + observation["cubes"]["cube_2"]["position_world"] = [goal] + observation["ee_positions_world"] = [[[0.45, -0.20, 0.13]]] + self.assertFalse(pick_place_success.is_success(observation)) + observation["ee_positions_world"] = [[[0.45, -0.20, 0.20]]] + self.assertTrue(pick_place_success.is_success(observation)) + + +class SuccessSnapshotTests(unittest.TestCase): + def test_callback_refreshes_scene_after_episode_boundary(self): + from isaaccapture_examples.synthetic_xr.franka import task_hooks + + source = Path(task_hooks.__file__).with_name("observe_record_demos.py") + tree = ast.parse(source.read_text()) + callback = next( + node + for node in ast.walk(tree) + if isinstance(node, ast.FunctionDef) and node.name == "custom_success" + ) + # Reproduce the live failure: reset ID advanced, old scene still cached + # because render publication was throttled. The old goal is blocked. + old = XRExportRaceTests.Session().observe() + old["cubes"]["cube_2"]["position_world"] = [[0.45, -0.04, 0.02]] + fresh = XRExportRaceTests.Session().observe() + stale = copy.deepcopy(old) + pick_place_success._history.clear() + self.assertFalse(pick_place_success.is_success(stale)) + self.assertIsNone(pick_place_success._history["goal"]) + pick_place_success._history.clear() + + def publish(*, force): + self.assertTrue(force) + stale.update(copy.deepcopy(fresh)) + + namespace = { + "state": stale, + "publish": publish, + "evaluate_success": task_hooks.evaluate_success, + "success_callback": pick_place_success.is_success, + } + compiled = compile( + ast.Module(body=[callback], type_ignores=[]), str(source), "exec" + ) + exec(compiled, namespace) + torch = SimpleNamespace(tensor=lambda value, **_: value, bool=bool) + with patch.dict("sys.modules", {"torch": torch}): + self.assertEqual( + namespace["custom_success"](SimpleNamespace(device="cpu")), [False] + ) + self.assertIsNotNone(pick_place_success._history["goal"]) + self.assertEqual( + pick_place_success._history["start"].tolist(), + fresh["cubes"]["cube_2"]["position_world"][0], + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/examples/synthetic_xr/tests/test_task_guards.py b/examples/synthetic_xr/tests/test_task_guards.py new file mode 100644 index 0000000000..6f003a8a5e --- /dev/null +++ b/examples/synthetic_xr/tests/test_task_guards.py @@ -0,0 +1,373 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Counterexamples for motion admission, measured poses and episode boundaries.""" + +import copy +import io +import json +from pathlib import Path +from tempfile import TemporaryDirectory +import unittest +from unittest.mock import Mock, patch + +import numpy as np + +from isaaccapture_examples.synthetic_xr.franka.session import ( + FrankaTaskSession, + validate_observation, +) +from isaaccapture_examples.synthetic_xr.franka.xr_controller_bridge import ( + XRControllerBridge, +) +from isaaccapture_examples.synthetic_xr.franka.task_hooks import install_reset_observer +from test_task_session import observation + +BRIDGE = "isaaccapture_examples.synthetic_xr.franka.xr_controller_bridge" + + +def bridge(client=None): + value = XRControllerBridge.__new__(XRControllerBridge) + value.base_to_world = np.eye(4) + value.base_to_anchor = np.eye(4) + value.aim_to_grip = np.eye(4) + value.offset = np.eye(3) + value.target = np.eye(4) + value.target[:3, 3] = [0.4, 0, 0.2] + value.trigger = 0.0 + value.ack_trace = io.StringIO() + value.client = client or Mock() + return value + + +def feedback(step): + return dict( + steps=step, + timestamp=101.0, + simulation_dt=0.05, + exported_successful_episodes=0, + last_action=[[0.4, 0, 0.2, 0, 0, 0, 1, 1]], + ) + + +class MotionGuards(unittest.TestCase): + def test_invalid_endpoint_or_trigger_has_no_input_side_effects(self): + for point, rotation, trigger in ( + ([1.6, 0, 0.2], np.eye(3), None), + ([0.4, 0, 0.2], np.eye(3), float("nan")), + ([0.4, 0, 0.2], np.eye(3), 2), + ([0.4, 0, 0.2], np.diag([1, 1, -1]), None), + ): + with self.subTest(point=point, trigger=trigger): + value = bridge() + initial = value.target.copy() + value.client.state.return_value = feedback(10) + real_target = value.target_base + # Bind the callable to this subtest, not the loop variable. + value.target_base = lambda p, r, t, target=real_target: target( + p, r, t, wait_receipt=False + ) + with patch(BRIDGE + ".wait_steps"), self.assertRaises(ValueError): + value.move_world(point, rotation, trigger) + value.client.command.assert_not_called() + np.testing.assert_array_equal(value.target, initial) + self.assertEqual(value.trigger, 0) + + def test_workspace_is_checked_after_world_to_base_transform(self): + value = bridge() + value.base_to_world[:3, 3] = [-2, 0, 0] + value.client.state.return_value = feedback(10) + value.target_base = Mock() + with patch(BRIDGE + ".wait_steps"): + value.move_world([2.5, 0, 0.2], np.eye(3)) + np.testing.assert_allclose(value.target_base.call_args.args[0], [0.5, 0, 0.2]) + value.target_base.reset_mock() + with self.assertRaises(ValueError): + value.move_world([0.4, 0, 0.2], np.eye(3)) + value.target_base.assert_not_called() + + def test_public_tool_offset_endpoint_is_checked_before_input(self): + value = bridge() + value.client.state.return_value = feedback(10) + real_target = value.target_base + value.target_base = lambda p, r, t: real_target(p, r, t, wait_receipt=False) + session = FrankaTaskSession(observation_dir="/tmp/observer", output="/tmp/task") + session.controller = value + # This tool target maps outside the hand workspace after its offset. + with patch(BRIDGE + ".wait_steps"), self.assertRaises(ValueError): + session.move_tool([0, 0, -1.45], [0, 0, 0, 1]) + value.client.command.assert_not_called() + + def test_position_alone_cannot_claim_pose_convergence(self): + session = FrankaTaskSession(observation_dir="/tmp/observer", output="/tmp/task") + session.controller = bridge() + session.controller.move_world = Mock() + session.state = Mock(return_value=observation(Path("/tmp/observer"))) + with self.assertRaisesRegex(RuntimeError, "converge"): + session.move_tool([0.4, 0, 0.2], [1, 0, 0, 0]) + + def test_orientation_only_move_waits_and_quaternion_sign_is_equivalent(self): + session = FrankaTaskSession(observation_dir="/tmp/observer", output="/tmp/task") + session.controller = bridge() + session.controller.move_world = Mock() + wrong = observation(Path("/tmp/observer")) + correct = copy.deepcopy(wrong) + correct["ee_quaternions_world"] = [[[-1, 0, 0, 0]]] + session.state = Mock(side_effect=[wrong, correct]) + self.assertIs(session.move_tool([0.4, 0, 0.2], [1, 0, 0, 0]), correct) + self.assertEqual(session.controller.move_world.call_count, 2) + + def test_measured_quaternion_and_counters_are_validated(self): + for name, invalid in ( + ("ee_quaternions_world", [[[0, 0, 0, 0]]]), + ("episode_id", -1), + ("steps", True), + ("exported_successful_episodes", 0.5), + ): + with self.subTest(name=name): + value = observation(Path("/tmp/observer")) + value[name] = invalid + with self.assertRaises(ValueError): + validate_observation(value) + + def test_reset_and_step_regression_latch_failure_and_release_input(self): + for changes in ({"episode_id": 2, "steps": 11}, {"steps": 9}): + with self.subTest(changes=changes), TemporaryDirectory() as directory: + root = Path(directory) + session = FrankaTaskSession(observation_dir=root, output=root / "task") + session.run_id = "one" + value = observation(root) + session.client.state = Mock(return_value=value) + session.client.command = Mock(return_value={}) + session.observe() + value.update(changes) + with self.assertRaisesRegex(RuntimeError, "Episode reset"): + session.observe() + with self.assertRaisesRegex(RuntimeError, "Episode reset"): + session.command("pose", buttons={"trigger": 1}) + session.client.command.assert_called_once_with("stop") + + def test_reset_during_native_ack_aborts_remaining_interpolation(self): + with TemporaryDirectory() as directory: + root = Path(directory) + output = root / "task" + output.mkdir() + session = FrankaTaskSession(observation_dir=root, output=output) + session.run_id = "one" + value = observation(root) + session.client.state = Mock(return_value=value) + session.observe() + session.controller = bridge(session) + actions = [] + + def send(action, **kwargs): + actions.append(action) + if action == "pose": + value.update(episode_id=2, steps=11) + return {"timestamp": 0, "id": "command"} + + session.client.command = Mock(side_effect=send) + (root / "sim-state.json").write_text(json.dumps(value)) + with self.assertRaisesRegex(RuntimeError, "Episode reset") as caught: + session.move_tool([0.5, 0, 0.2], [0, 0, 0, 1]) + self.assertEqual(actions, ["pose", "stop"]) + session.__exit__(RuntimeError, caught.exception, None) + self.assertEqual( + json.loads((output / "result.json").read_text())["status"], "FAIL" + ) + self.assertTrue((root / "stop").exists()) + + def test_reset_during_wait_is_rejected(self): + with TemporaryDirectory() as directory: + root = Path(directory) + session = FrankaTaskSession(observation_dir=root, output=root / "task") + session.run_id = "one" + value = observation(root) + reset = dict(value, episode_id=2, steps=11) + session.client.state = Mock(side_effect=[value, value, reset]) + session.client.command = Mock(return_value={}) + with self.assertRaisesRegex(RuntimeError, "Episode reset"): + session.wait(1) + session.client.command.assert_called_once_with("stop") + + def test_terminal_export_reset_does_not_rearm_motion(self): + with TemporaryDirectory() as directory: + root = Path(directory) + session = FrankaTaskSession(observation_dir=root, output=root / "task") + session.run_id = "one" + value = observation(root) + session.client.state = Mock(return_value=value) + session.client.command = Mock(return_value={}) + session.observe() + value.update(episode_id=2, steps=0, exported_successful_episodes=1) + session.controller = bridge(session) + session.controller.move_world = Mock() + self.assertIs(session.move_tool([0.4, 0, 0.2], [0, 0, 0, 1]), value) + with self.assertRaisesRegex(RuntimeError, "already completed"): + session.command("pose", buttons={"trigger": 1}) + session.client.command.assert_not_called() + + def test_export_between_preflight_and_command_sends_no_input(self): + with TemporaryDirectory() as directory: + root = Path(directory) + session = FrankaTaskSession(observation_dir=root, output=root / "task") + session.run_id = "one" + value = observation(root) + exported = dict(value, episode_id=2, exported_successful_episodes=1) + session.client.state = Mock(side_effect=[value, exported]) + session.client.command = Mock() + value_bridge = bridge(session) + self.assertIsNone(value_bridge.target_base([0.4, 0, 0.2], np.eye(3))) + session.client.command.assert_not_called() + + def test_render_timestamp_cannot_acknowledge_unchanged_native_step(self): + value = bridge() + value.client.state.side_effect = [feedback(7), feedback(7), feedback(8)] + value.client.command.return_value = {"timestamp": 100.0, "id": "command"} + with patch(BRIDGE + ".time.sleep"): + value.target_base([0.4, 0, 0.2], np.eye(3)) + receipt = json.loads(value.ack_trace.getvalue()) + self.assertEqual(receipt["native_step"], 8) + self.assertEqual(receipt["native_step_before_send"], 7) + + def test_newer_session_command_preflight_is_used_for_ack_baseline(self): + value = bridge() + value.client.state.side_effect = [feedback(7), feedback(8), feedback(9)] + value.client.command.return_value = { + "timestamp": 100.0, + "id": "command", + "native_step_before_send": 8, + } + with patch(BRIDGE + ".time.sleep"): + value.target_base([0.4, 0, 0.2], np.eye(3)) + receipt = json.loads(value.ack_trace.getvalue()) + self.assertEqual(receipt["native_step"], 9) + self.assertEqual(receipt["native_step_before_send"], 8) + + def test_stale_matching_action_times_out_without_receipt(self): + value = bridge() + value.client.state.return_value = feedback(7) + value.client.command.return_value = {"timestamp": 100.0, "id": "command"} + with ( + patch(BRIDGE + ".time.sleep"), + patch(BRIDGE + ".time.monotonic", side_effect=[0, 1, 9]), + self.assertRaises(TimeoutError), + ): + value.target_base([0.4, 0, 0.2], np.eye(3)) + self.assertEqual(value.ack_trace.getvalue(), "") + + def test_reset_observer_covers_explicit_and_internal_resets_once(self): + class Environment: + def __init__(self): + self.epoch = 0 + self.seen = [] + self.result = object() + + def _reset_idx(self, ids, *, reason): + self.seen.append((ids, reason, self.epoch)) + return self.result + + def reset(self): + return self._reset_idx([0], reason="manual") + + def step(self): + return self._reset_idx([0], reason="termination") + + env = Environment() + install_reset_observer(env, lambda: setattr(env, "epoch", env.epoch + 1)) + self.assertIs(env.reset(), env.result) + self.assertIs(env.step(), env.result) + self.assertIs(env._reset_idx([], reason="empty"), env.result) + self.assertEqual( + env.seen, [([0], "manual", 1), ([0], "termination", 2), ([], "empty", 2)] + ) + + def test_native_default_and_partial_reset_slices_preserve_arguments(self): + class Environment: + num_envs = 3 + + def __init__(self): + self.seen = [] + + def _reset_idx(self, ids): + self.seen.append(ids) + return ids + + def reset(self, env_ids=slice(None)): + if env_ids is None: + env_ids = slice(None) + return self._reset_idx(env_ids) + + env = Environment() + callback = Mock() + install_reset_observer(env, callback) + for selection in (slice(None), None, slice(1, 2), slice(None, None, -1)): + result = env.reset(selection) + self.assertEqual(result, slice(None) if selection is None else selection) + self.assertEqual(callback.call_count, 4) + callback.reset_mock() + for empty in (slice(0, 0), slice(3, 3), slice(2, 1)): + self.assertIs(env.reset(empty), empty) + callback.assert_not_called() + # Preserve None as well if a native caller accepts it directly. + self.assertIsNone(env._reset_idx(None)) + callback.assert_called_once_with() + + def test_internal_reset_blocks_next_command_without_explicit_reset(self): + with TemporaryDirectory() as directory: + root = Path(directory) + session = FrankaTaskSession(observation_dir=root, output=root / "task") + session.run_id = "one" + value = observation(root) + session.client.state = Mock(return_value=value) + session.client.command = Mock(return_value={}) + session.observe() + + class Environment: + def reset(self): + raise AssertionError("Automatic reset must not call env.reset") + + def _reset_idx(self, ids): + return ids + + def step(self): + self._reset_idx([0]) + + env = Environment() + install_reset_observer( + env, lambda: value.update(episode_id=value["episode_id"] + 1) + ) + env.step() + value["steps"] += 1 + with self.assertRaisesRegex(RuntimeError, "Episode reset"): + session.command("pose", buttons={"trigger": 1}) + session.client.command.assert_called_once_with("stop") + + def test_reset_observer_preserves_native_exception(self): + env = Mock() + error = RuntimeError("native reset failed") + env._reset_idx = Mock(side_effect=error) + callback = Mock() + install_reset_observer(env, callback) + with self.assertRaises(RuntimeError) as caught: + env._reset_idx([0]) + self.assertIs(caught.exception, error) + callback.assert_called_once_with() + + def test_session_receipt_records_pre_send_physics_step(self): + with TemporaryDirectory() as directory: + root = Path(directory) + session = FrankaTaskSession(observation_dir=root, output=root / "task") + session.run_id = "one" + session.client.state = Mock(return_value=observation(root)) + session.client.command = Mock(return_value={"id": "command"}) + self.assertEqual( + session.command("pose", buttons={"trigger": 0})[ + "native_step_before_send" + ], + 10, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/examples/synthetic_xr/tests/test_task_session.py b/examples/synthetic_xr/tests/test_task_session.py index f06067c2cb..6f4a73a059 100644 --- a/examples/synthetic_xr/tests/test_task_session.py +++ b/examples/synthetic_xr/tests/test_task_session.py @@ -43,6 +43,8 @@ def observation(root): "phase": "running", "timestamp": time.time(), "simulation_dt": 0.05, + "episode_id": 1, + "steps": 10, "ee_positions_world": [[[0.4, 0, 0.2]]], "ee_quaternions_world": [[[0, 0, 0, 1]]], "cubes": { @@ -285,6 +287,7 @@ def test_invalid_tool_and_gripper_inputs_never_send_motion(self): def test_improper_rotation_rejected_before_browser_command(self): bridge = XRControllerBridge.__new__(XRControllerBridge) bridge.client = Mock() + bridge.trigger = 0.0 with self.assertRaises(ValueError): bridge.target_base([0.4, 0, 0.2], np.diag([1, 1, -1])) bridge.client.command.assert_not_called() From 4686d016bd692389a5afd249da08271a17c704d5 Mon Sep 17 00:00:00 2001 From: Sameer Tangade Date: Fri, 30 Oct 2026 19:11:25 +0000 Subject: [PATCH 11/11] Refresh WebXR lockfile after main merge Signed-off-by: Sameer Tangade --- deps/cloudxr/webxr_client/package-lock.json | 121 ++++++++++++++++---- 1 file changed, 98 insertions(+), 23 deletions(-) diff --git a/deps/cloudxr/webxr_client/package-lock.json b/deps/cloudxr/webxr_client/package-lock.json index cf0d7eb63d..52909c933f 100644 --- a/deps/cloudxr/webxr_client/package-lock.json +++ b/deps/cloudxr/webxr_client/package-lock.json @@ -23,6 +23,7 @@ "uuid": "^14.0.0" }, "devDependencies": { + "@iwer/devui": "2.7.0", "@playwright/test": "^1.63.0", "@types/jest": "^30", "@types/node": "^22", @@ -41,7 +42,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-simple-import-sort": "^12.1.1", "html-webpack-plugin": "^5.6.7", - "iwer": "^2.3.0", + "iwer": "^2.7.0", "jest": "^30", "jest-environment-jsdom": "^30.4.1", "prettier": "3.5.3", @@ -1341,29 +1342,30 @@ } }, "node_modules/@iwer/devui": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@iwer/devui/-/devui-1.1.2.tgz", - "integrity": "sha512-ggF1lXSX14BTYP0QzB4xaurySr2PC+3+rtK/dpCR++giWquzFv2mBw3LW/PaCtdl5mqkZMrQ2GSwfUNg9ZoO+w==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@iwer/devui/-/devui-2.7.0.tgz", + "integrity": "sha512-ilEXc/h+6SSlJiX6No9OKPekeO1lAAsXDHfaGZ6jFCuMUAxKopwDySYYn8Ld9vql8dh0QK39IpXdOKUiIMoGCw==", + "dev": true, "license": "MIT", "dependencies": { - "@fortawesome/fontawesome-svg-core": "6.6.0", - "@fortawesome/free-solid-svg-icons": "6.6.0", - "@fortawesome/react-fontawesome": "0.2.2", - "@pmndrs/handle": "^6.6.17", - "@pmndrs/pointer-events": "^6.6.17", - "react": ">=18.3.1", - "react-dom": ">=18.3.1", - "styled-components": "^6.1.13", - "three": "^0.165.0" + "@pmndrs/handle": "^6.6.29", + "@pmndrs/pointer-events": "^6.6.29", + "lucide-react": "^1.20.0", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "styled-components": "^6.4.1", + "three": "^0.184.0", + "zustand": "^5.0.13" }, "peerDependencies": { - "iwer": "^2.0.1" + "iwer": "^2.7.0" } }, "node_modules/@iwer/devui/node_modules/styled-components": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.6.0.tgz", "integrity": "sha512-drKKsqvDBxVRn4ySU67iQ5nQBuo/1xyYeBfdhDde112PkqrS3+95KPnpDs5hFRcmrW+q6+gbyywYYYR14oWFtw==", + "dev": true, "license": "MIT", "dependencies": { "@emotion/is-prop-valid": "1.4.0", @@ -1397,12 +1399,6 @@ } } }, - "node_modules/@iwer/devui/node_modules/three": { - "version": "0.165.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz", - "integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==", - "license": "MIT" - }, "node_modules/@iwer/sem": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@iwer/sem/-/sem-0.2.5.tgz", @@ -3012,6 +3008,69 @@ "three": "*" } }, + "node_modules/@pmndrs/xr/node_modules/@iwer/devui": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@iwer/devui/-/devui-1.1.2.tgz", + "integrity": "sha512-ggF1lXSX14BTYP0QzB4xaurySr2PC+3+rtK/dpCR++giWquzFv2mBw3LW/PaCtdl5mqkZMrQ2GSwfUNg9ZoO+w==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "6.6.0", + "@fortawesome/free-solid-svg-icons": "6.6.0", + "@fortawesome/react-fontawesome": "0.2.2", + "@pmndrs/handle": "^6.6.17", + "@pmndrs/pointer-events": "^6.6.17", + "react": ">=18.3.1", + "react-dom": ">=18.3.1", + "styled-components": "^6.1.13", + "three": "^0.165.0" + }, + "peerDependencies": { + "iwer": "^2.0.1" + } + }, + "node_modules/@pmndrs/xr/node_modules/@iwer/devui/node_modules/three": { + "version": "0.165.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz", + "integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==", + "license": "MIT" + }, + "node_modules/@pmndrs/xr/node_modules/styled-components": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.6.0.tgz", + "integrity": "sha512-drKKsqvDBxVRn4ySU67iQ5nQBuo/1xyYeBfdhDde112PkqrS3+95KPnpDs5hFRcmrW+q6+gbyywYYYR14oWFtw==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.4.0", + "@types/stylis": "4.2.7", + "css-to-react-native": "3.2.0", + "csstype": "3.2.3", + "stylis": "4.3.6" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "css-to-react-native": ">= 3.2.0", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-native": ">= 0.68.0" + }, + "peerDependenciesMeta": { + "css-to-react-native": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@pmndrs/xr/node_modules/zustand": { "version": "4.5.7", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", @@ -9045,9 +9104,9 @@ } }, "node_modules/iwer": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/iwer/-/iwer-2.6.0.tgz", - "integrity": "sha512-DNihqCixizrNjPKScTt0aOy9+YP1pn0Jxj1V6tBOV0ceRm6YrEMLo8QhEwYspPYJ9QZjDJShDMdZAaPXDPaSAw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/iwer/-/iwer-2.7.0.tgz", + "integrity": "sha512-zC4VfcwFenuaIU/VJgGfBcR2tfALtNZLSn2vZR7PnNU93S6d9bIK07DRuSMGMgdfPPln41stU/wZa6goWA6bRg==", "license": "MIT", "dependencies": { "gl-matrix": "^3.4.4", @@ -9942,6 +10001,22 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.65.0.tgz", + "integrity": "sha512-roSdtzudEY39i2hn5LLjA8toYfhF2z4S10WSqZF4ubXpHlOmNTvaxMxZLdJfQDi6pEifQWX+cRmYihKSsvAxfA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "@types/react": "*", + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/maath": { "version": "0.10.8", "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",