diff --git a/.gitattributes b/.gitattributes index 9f5971d..ced284c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,3 +10,4 @@ *.las binary *.lcc binary apps/control-station/vendor/rerun-web-viewer-0.34.1/re_viewer_bg.nodedc.wasm filter=lfs diff=lfs merge=lfs -text +apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer_bg.nodedc.wasm filter=lfs diff=lfs merge=lfs -text diff --git a/apps/control-station/package-lock.json b/apps/control-station/package-lock.json index c84a6cb..8b06169 100644 --- a/apps/control-station/package-lock.json +++ b/apps/control-station/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "@nodedc/mission-core-control-station", "version": "0.1.0", + "hasInstallScript": true, "dependencies": { "@noble/hashes": "^2.2.0", "@nodedc/map-cesium-react": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/map-cesium-react", diff --git a/apps/control-station/package.json b/apps/control-station/package.json index 8182250..6fc0a42 100644 --- a/apps/control-station/package.json +++ b/apps/control-station/package.json @@ -5,6 +5,8 @@ "type": "module", "scripts": { "dev": "vite", + "postinstall": "node scripts/install-rerun-navigation.mjs", + "prebuild": "node scripts/install-rerun-navigation.mjs", "build": "tsc -b && vite build", "preview": "vite preview", "test:unit": "node --test test/*.test.mjs", diff --git a/apps/control-station/scripts/install-rerun-navigation.mjs b/apps/control-station/scripts/install-rerun-navigation.mjs new file mode 100644 index 0000000..642a88d --- /dev/null +++ b/apps/control-station/scripts/install-rerun-navigation.mjs @@ -0,0 +1,59 @@ +import { createHash } from "node:crypto"; +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const packageRoot = resolve(root, "node_modules/@rerun-io/web-viewer"); +const vendorRoot = resolve(root, "vendor/rerun-web-viewer-0.36.3"); +const manifest = JSON.parse(readFileSync(resolve(vendorRoot, "navigation-build.json"), "utf8")); +const installed = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8")); +const sha = bytes => createHash("sha256").update(bytes).digest("hex"); +if (installed.version !== "0.36.3" || manifest.upstreamVersion !== installed.version) { + throw new Error("Rerun navigation requires the audited 0.36.3 package; rebase before upgrading"); +} +if (sha(readFileSync(resolve(vendorRoot, "NODEDC_NAVIGATION.patch"))) !== manifest.patchSha256) { + throw new Error("Rerun navigation source patch identity mismatch"); +} + +const eyeType = "{ position: [number, number, number]; lookTarget: [number, number, number]; eyeUp: [number, number, number] } | null"; +const additions = { + "index.js": { + marker: " get_active_recording_id() {", + code: ` get_camera_eye() {\n if (!this.#handle) throw new Error("Rerun viewer is stopped");\n return JSON.parse(this.#handle.nodedc_camera_eye() ?? "null");\n }\n`, + }, + "index.ts": { + marker: " get_active_recording_id(): string | null {", + code: ` get_camera_eye(): ${eyeType} {\n if (!this.#handle) throw new Error("Rerun viewer is stopped");\n return JSON.parse(this.#handle.nodedc_camera_eye() ?? "null");\n }\n`, + }, + "index.d.ts": { + marker: " get_active_recording_id(): string | null;", + code: ` get_camera_eye(): ${eyeType};\n`, + }, +}; + +// Validate the complete set before changing any installed file. A package +// upgrade, corrupt artifact or independently modified wrapper fails closed. +const writes = []; +for (const [name, identity] of Object.entries(manifest.files)) { + const current = readFileSync(resolve(packageRoot, name)); + let output; + if (additions[name]) { + const { marker, code } = additions[name]; + const source = current.toString("utf8"); + const original = source.includes(code) ? source.replace(code, "") : source; + if (sha(original) !== identity.upstreamSha256 || original.split(marker).length !== 2) { + throw new Error(`Unexpected upstream wrapper: ${name}`); + } + output = Buffer.from(original.replace(marker, code + marker)); + } else { + output = readFileSync(resolve(vendorRoot, identity.artifact)); + if (sha(output) !== identity.sha256) throw new Error(`Corrupt navigation artifact: ${name}`); + if (![identity.upstreamSha256, identity.sha256].includes(sha(current))) { + throw new Error(`Unexpected installed runtime: ${name}`); + } + } + writes.push([resolve(packageRoot, name), output]); +} +for (const [path, output] of writes) writeFileSync(path, output); +console.log("Installed NODE.DC Rerun 0.36.3 navigation/v1 (native camera, one renderer)."); diff --git a/apps/control-station/src/App.tsx b/apps/control-station/src/App.tsx index dfd639c..30e6c14 100644 --- a/apps/control-station/src/App.tsx +++ b/apps/control-station/src/App.tsx @@ -14,6 +14,7 @@ import { Inspector, StatusBadge, TextField, + ToastStack, UserProfileMenu, Window, WindowFooterActions, @@ -49,6 +50,7 @@ import type { } from "./core/observation/sessionArchive"; import { useRecordedSessionAdmission } from "./core/observation/useRecordedSessionAdmission"; import { useWorkspaceLayoutProfile } from "./core/observation/useWorkspaceLayoutProfile"; +import { useSessionDisplayProfile } from "./core/observation/useSessionDisplayProfile"; import { viewerSettingsTargetIdentity } from "./core/observation/viewerSettingsTarget"; import { resolvePolygonRunRoute } from "./core/polygon/runArchive"; import { @@ -181,6 +183,7 @@ export default function App() { const appliedProfileKeyRef = useRef(null); const sceneSettingsRef = useRef(defaultSceneSettings); const displayDraftRef = useRef(defaultSceneSettings); + const closeDisplayRef = useRef<() => void>(() => {}); const confirmedSceneSettingsRef = useRef(defaultSceneSettings); const viewerSettingsCommitTimerRef = useRef(null); const runtimeUpdateViewerSettingsRef = useRef(runtime.updateViewerSettings); @@ -228,6 +231,13 @@ export default function App() { ); runtimeUpdateViewerSettingsRef.current = runtime.updateViewerSettings; replayActiveRef.current = replayPresented; + const sessionDisplayProfile = useSessionDisplayProfile(replayPresented ? recordedReplay?.sessionId ?? null : null, (settings) => { + sceneSettingsRef.current = settings; + displayDraftRef.current = settings; + confirmedSceneSettingsRef.current = settings; + setSceneSettings(settings); + setDisplayDraft(settings); + }); useEffect(() => { if (!polygonDatasetRoute.active || polygonDatasetRouteOpenedRef.current) return; @@ -310,6 +320,7 @@ export default function App() { const remote = runtime.state?.viewerSettings; if ( !remote || + replayActiveRef.current || workspaceLayoutProfile.profile || sceneSettingsCommitterRef.current?.isBusy() ) return; @@ -332,7 +343,7 @@ export default function App() { useEffect(() => { const profile = workspaceLayoutProfile.profile; - if (!profile) return; + if (!profile || selectedRecordedSessionIdRef.current) return; if (viewerSettingsCommitTimerRef.current !== null) { window.clearTimeout(viewerSettingsCommitTimerRef.current); viewerSettingsCommitTimerRef.current = null; @@ -403,7 +414,10 @@ export default function App() { const closeSceneWindow = useCallback((windowId: SceneToolWindowId) => { if (windowId === "sources") setSourceWindowOpen(false); - if (windowId === "display") setDisplayWindowOpen(false); + if (windowId === "display") { + setDisplayWindowOpen(false); + closeDisplayRef.current(); + } if (windowId === "layers") setLayerInspectorOpen(false); setSceneWindowOrder((current) => current.filter((candidate) => candidate !== windowId)); }, []); @@ -464,14 +478,22 @@ export default function App() { const next = { ...displayDraftRef.current, ...patch }; displayDraftRef.current = next; setDisplayDraft(next); + sessionDisplayProfile.edited(); if (viewerSettingsCommitTimerRef.current !== null) { window.clearTimeout(viewerSettingsCommitTimerRef.current); } + // Preview and persistence are separate. An open inspector must not freeze + // either its controls or the shared accumulation slider below the scene. + if (replayActiveRef.current && (patch.pointDecimationPercent === 0 || patch.pointDecimationPercent === 100)) { + viewerSettingsCommitTimerRef.current = null; + sceneSettingsCommitterRef.current?.enqueue(next); + return; + } viewerSettingsCommitTimerRef.current = window.setTimeout(() => { viewerSettingsCommitTimerRef.current = null; sceneSettingsCommitterRef.current?.enqueue(displayDraftRef.current); }, viewerSettingsQuietPeriodMs); - }, []); + }, [sessionDisplayProfile.edited]); const flushDisplaySettings = useCallback(() => { if (viewerSettingsCommitTimerRef.current === null) return; @@ -481,8 +503,15 @@ export default function App() { }, []); const commitDisplayPatch = useCallback((patch: Partial) => { + sessionDisplayProfile.edited(); commitDisplaySettings({ ...displayDraftRef.current, ...patch }); - }, [commitDisplaySettings]); + }, [commitDisplaySettings, sessionDisplayProfile.edited]); + + closeDisplayRef.current = () => { + const settings = {...displayDraftRef.current}; + commitDisplaySettings(settings); + if (replayActiveRef.current) sessionDisplayProfile.save(settings); + }; const openDisplay = () => { if (!sceneWorkspaceActive) return; @@ -1036,8 +1065,11 @@ export default function App() { onClose={() => closeSceneWindow("display")} > + commitDisplayPatch={replayPresented ? stageDisplayPatch : commitDisplayPatch} flushDisplaySettings={flushDisplaySettings} replayPresented={replayPresented}/> + (null); const sessions = useObservationSessions({ limit, - scope: "source", + scope: "standalone", replayEnabled: blockedReason === null, onReplayBegin, onReplayAccepted, diff --git a/apps/control-station/src/components/RerunViewport.tsx b/apps/control-station/src/components/RerunViewport.tsx index 5038db7..b6a2b58 100644 --- a/apps/control-station/src/components/RerunViewport.tsx +++ b/apps/control-station/src/components/RerunViewport.tsx @@ -8,6 +8,7 @@ import { } from "./rerun/recordedRerunCameraJournal"; import type { SceneSettings } from "../sceneSettings"; +import {useRecordedPointDisplay} from './rerun/useRecordedPointDisplay'; import { advanceLiveReceiverOpenWatchdog, advanceLiveReceiverWatchdog, @@ -130,6 +131,7 @@ export interface RerunViewportProps { onPlaybackControllerChange?: (controller: RerunPlaybackController | null) => void; sceneSettings?: Pick< SceneSettings, + | "pointDecimationPercent" | "accumulationSeconds" | "showGrid" | "showPoints" @@ -148,14 +150,8 @@ interface RerunBlueprintChannel { cameraContract?: string | null; appliedFollowTrajectory?: boolean | null; pendingFollowCameraEye?: RecordedRerunCameraEye | null; - configureCameraJournal?: ( - eye: RecordedRerunCameraEye, - spatialViewportStart: number, - ) => void; - getCameraEye?: () => RecordedRerunCameraEye; - setCameraViewportStart?: (spatialViewportStart: number) => void; + getCameraEye?: () => RecordedRerunCameraEye | null; getCurrentTimeNs?: () => number | null; - setCameraMaxOrbitalRadius?: (maxOrbitalRadius: number) => void; channel: { readonly ready: boolean; send_rrd: (rrdBytes: Uint8Array) => void; @@ -421,6 +417,7 @@ export async function fetchRecordedBlueprintRrd( currentTimeNs, reactivateUpdates = false, onCameraMaxOrbitalRadius, + displayPointBank, perceptionLayers = { enabled: false, detections2d: false, @@ -444,12 +441,17 @@ export async function fetchRecordedBlueprintRrd( currentTimeNs?: number | null; reactivateUpdates?: boolean; onCameraMaxOrbitalRadius?: (maxOrbitalRadius: number) => void; + displayPointBank?: string | null; perceptionLayers?: RecordedPerceptionLayers; fetcher?: typeof globalThis.fetch; }, ): Promise { const resolvedUnifiedPerception = unifiedPerception ?? (perceptionLayers.enabled && activeView !== "spatial"); + // Rerun exposes its interpolated TimeReal cursor as f64 nanoseconds, even + // when paused after seeking. The API needs an integer pose-query timestamp. + // Quantize at this boundary; never round the viewer's actual playback cursor. + const requestTimeNs = currentTimeNs == null ? currentTimeNs : Math.round(currentTimeNs); const base = new URL(origin); const endpoint = new URL(endpointUrl, base.origin); if ( @@ -481,7 +483,7 @@ export async function fetchRecordedBlueprintRrd( ...cameraEye.eyeUp, ].some((value) => !Number.isFinite(value))) || (currentTimeNs !== undefined && currentTimeNs !== null && ( - !Number.isSafeInteger(currentTimeNs) || currentTimeNs < 0 + !Number.isSafeInteger(requestTimeNs) || currentTimeNs < 0 )) || [ perceptionLayers.enabled, @@ -508,6 +510,7 @@ export async function fetchRecordedBlueprintRrd( application_id: identity.applicationId, recording_id: identity.recordingId, blueprint_session_id: blueprintSessionId, + ...(displayPointBank == null ? {} : {display_point_bank: displayPointBank}), accumulation_seconds: settings.accumulationSeconds, show_grid: settings.showGrid, show_points: settings.showPoints, @@ -528,7 +531,7 @@ export async function fetchRecordedBlueprintRrd( eye_up: cameraEye?.eyeUp ?? null, ...(eyeRelativeToTracking ? { eye_relative_to_tracking: true } : {}), ...(currentTimeNs === undefined || currentTimeNs === null ? {} : { - current_time_ns: currentTimeNs, + current_time_ns: requestTimeNs, }), show_detections_2d: perceptionLayers.detections2d, show_camera_image: perceptionLayers.cameraImage ?? true, @@ -585,10 +588,8 @@ export function recordedCameraJournalContract( followTrajectory: boolean; }, ): string { - // Following is a tracking property of the current native eye. It must never - // initialize the browser journal again: doing so replaces the operator's - // current pose with the startup preset immediately before the blueprint is - // sent. Plan/3D and explicit reset are the only preset transitions. + // Following uses the native eye snapshot, never the startup preset. + // Plan/3D and explicit reset are the only preset transitions. return [activeView, viewResetGeneration, planView].join(":"); } @@ -841,6 +842,7 @@ function RerunViewportInstance({ const presentationGateRef = useRef(presentationGate); presentationGateRef.current = presentationGate; const [blueprintChannelRevision, setBlueprintChannelRevision] = useState(0); + const [pointDisplayVisibilityGate, setPointDisplayVisibilityGate] = useState(''); const [perceptionChannelRevision, setPerceptionChannelRevision] = useState(0); const recordedBlueprintUrl = sourceUrl ? resolveRecordedBlueprintUrl( @@ -857,6 +859,17 @@ function RerunViewportInstance({ const recordedPointColorsUrl = recordedBlueprintUrl ? recordedBlueprintUrl.replace(/\/blueprint\.rrd$/, "/point-colors.rrd") : sourceUrl ? resolveRecordedPointColorsUrl(sourceUrl, window.location.origin) : null; + const pointDisplay = useRecordedPointDisplay({ + endpoint: recordedBlueprintUrl, settings: sceneSettings, revision: blueprintChannelRevision, + sourceGeneration: recordedArtifact?.sha256, + ready: status === 'ready' && pointDisplayVisibilityGate === `${recordedBlueprintUrl}:${blueprintChannelRevision}`, + onLoad: onPointColorLoadChange, + getOwner: () => { + const active = blueprintChannelRef.current; + const identity = recordedIdentityRef.current; + return active?.channel.ready && identity ? {identity, send: bytes => active.channel.send_rrd(bytes)} : null; + }, + }); const presentationStatus = rerunPresentationStatus( status, presentationGate, @@ -1638,29 +1651,14 @@ function RerunViewportInstance({ cameraContract: null, appliedFollowTrajectory: null, pendingFollowCameraEye: null, - configureCameraJournal: (eye, spatialViewportStart) => { - if ("configure_camera_journal" in viewer) { - viewer.configure_camera_journal(eye, spatialViewportStart); - } - }, getCameraEye: () => "get_camera_eye" in viewer ? viewer.get_camera_eye() - : RECORDED_RERUN_ORBITAL_EYE, - setCameraViewportStart: (spatialViewportStart) => { - if ("set_camera_viewport_start" in viewer) { - viewer.set_camera_viewport_start(spatialViewportStart); - } - }, + : null, getCurrentTimeNs: () => { const currentIdentity = recordedIdentityRef.current; if (!currentIdentity) return null; return viewer.get_current_time(currentIdentity.recordingId, "session_time"); }, - setCameraMaxOrbitalRadius: (maxOrbitalRadius) => { - if ("set_camera_max_orbital_radius" in viewer) { - viewer.set_camera_max_orbital_radius(maxOrbitalRadius); - } - }, channel, }; blueprintChannelRef.current = blueprintChannel; @@ -2010,6 +2008,7 @@ function RerunViewportInstance({ useEffect(() => { if (!recordedPointColorsUrl || !sceneSettings) return; + if ((sceneSettings.pointDecimationPercent ?? 0) > 0) return; const active = blueprintChannelRef.current; const identity = recordedIdentityRef.current; if ( @@ -2089,6 +2088,7 @@ function RerunViewportInstance({ sceneSettings?.colorMode, sceneSettings?.customColor, sceneSettings?.palette, + sceneSettings?.pointDecimationPercent, ]); useEffect(() => { @@ -2113,12 +2113,6 @@ function RerunViewportInstance({ }, ); const cameraContractChanged = active.cameraContract !== cameraContract; - if (cameraContractChanged) { - active.configureCameraJournal?.( - recordedPlanView ? RECORDED_RERUN_PLAN_EYE : RECORDED_RERUN_ORBITAL_EYE, - recordedUnifiedPerception ? recordedUnifiedCameraShare : 0, - ); - } const abort = new AbortController(); const previousFollow = active.appliedFollowTrajectory ?? false; const enablingFollow = recordedFollowTrajectory && !previousFollow; @@ -2143,7 +2137,9 @@ function RerunViewportInstance({ if (eyeRelativeToTracking && currentTimeNs == null) { throw new Error("Recorded tracking cursor is unavailable"); } - return fetchRecordedBlueprintRrd(recordedBlueprintUrl, sceneSettings, identity, { + return fetchRecordedBlueprintRrd(recordedBlueprintUrl, + {...sceneSettings, showPoints: sceneSettings.showPoints && !pointDisplay.hidePoints}, identity, { + displayPointBank: pointDisplay.bank, origin: window.location.origin, blueprintSessionId: blueprintSessionIdRef.current, signal: abort.signal, @@ -2157,13 +2153,11 @@ function RerunViewportInstance({ planView: recordedPlanView, cameraEye, eyeRelativeToTracking, - currentTimeNs, + // Only following-eye transitions need a pose at the cursor. Supplying + // time for display-only updates makes the API scan the entire archive + // for camera bounds, although neither the camera nor its pose changed. + currentTimeNs: eyeRelativeToTracking ? currentTimeNs : undefined, reactivateUpdates, - onCameraMaxOrbitalRadius: (maxOrbitalRadius) => { - if (blueprintChannelRef.current === active) { - active.setCameraMaxOrbitalRadius?.(maxOrbitalRadius); - } - }, }); }; const canApply = () => ( @@ -2175,24 +2169,29 @@ function RerunViewportInstance({ const applyPayload = (payload: Uint8Array) => { if (!canApply()) return false; active.channel.send_rrd(payload); - active.setCameraViewportStart?.( - recordedUnifiedPerception ? recordedUnifiedCameraShare : 0, - ); + // Exclude inactive point generations before admitting their data into + // cached recordings whose embedded blueprint predates display controls. + setPointDisplayVisibilityGate(`${recordedBlueprintUrl}:${blueprintChannelRevision}`); return true; }; void (async () => { const firstEye = disablingFollow ? transitionEye ?? undefined : !enablingFollow && (cameraContractChanged || pendingFollowEye) - ? pendingFollowEye ?? active.getCameraEye?.() - : undefined; + ? pendingFollowEye ?? (recordedPlanView ? RECORDED_RERUN_PLAN_EYE : RECORDED_RERUN_ORBITAL_EYE) + : !enablingFollow ? active.getCameraEye?.() ?? undefined : undefined; const firstEyeIsTrackingRelative = Boolean(firstEye) && ( disablingFollow || recordedFollowTrajectory ); const firstPayload = await requestBlueprint( firstEye, firstEyeIsTrackingRelative, - enablingFollow || disablingFollow || Boolean(pendingFollowEye), + // Upstream copies incoming blueprints into an active store. Every + // settings update must activate it; merely appending rows is invisible. + // Activation copies the incoming blueprint store, not the operator's + // edited store. Preserve its actual native eye on display-only updates; + // omitting the eye resurrects the embedded startup camera. + true, ); if (!applyPayload(firstPayload)) return; active.cameraContract = cameraContract; @@ -2213,9 +2212,10 @@ function RerunViewportInstance({ const stabilizedPayload = await requestBlueprint(transitionEye, true, true); if (!applyPayload(stabilizedPayload)) return; active.pendingFollowCameraEye = null; - })().catch(() => { + })().catch((error: unknown) => { // The recording remains usable with its embedded default blueprint. // A later settings change retries through the same small channel. + if (!abort.signal.aborted) console.warn("Recorded scene settings were not applied", error); }); return () => abort.abort(); }, [ @@ -2243,6 +2243,9 @@ function RerunViewportInstance({ sceneSettings?.showPoints, sceneSettings?.showTrajectory, sceneSettings?.showGrid, + pointDisplay.ready, + pointDisplay.bank, + pointDisplay.hidePoints, ]); return ( diff --git a/apps/control-station/src/components/missions/MissionZonePreview.tsx b/apps/control-station/src/components/missions/MissionZonePreview.tsx index 43a73ce..c74f27a 100644 --- a/apps/control-station/src/components/missions/MissionZonePreview.tsx +++ b/apps/control-station/src/components/missions/MissionZonePreview.tsx @@ -2,11 +2,11 @@ import type {ReactNode} from "react"; import { Button, LoadingRegion } from "@nodedc/ui-react"; import { useSessionOverview } from "../../core/observation/useSessionOverview"; import { SessionOverviewScene } from "../observation/SessionOverviewScene"; -export function MissionZonePreview({ sessionId, toolbar }: { sessionId: string; toolbar?:ReactNode }) { +export function MissionZonePreview({ sessionId, generation, toolbar }: { sessionId: string; generation: string; toolbar?:ReactNode }) { const { data, error, retry } = useSessionOverview(sessionId); const pending = !error && (!data || data.state === "queued" || data.state === "preparing"); const failure = error || (data?.state === "error" ? data.message : null); if (pending) return ; if (failure || !data?.scene_url) return
{failure || "Облако записи недоступно."}
; - return ; + return ; } diff --git a/apps/control-station/src/components/missions/PlanningProjectSettings.tsx b/apps/control-station/src/components/missions/PlanningProjectSettings.tsx index f144f61..856dd02 100644 --- a/apps/control-station/src/components/missions/PlanningProjectSettings.tsx +++ b/apps/control-station/src/components/missions/PlanningProjectSettings.tsx @@ -1,53 +1,30 @@ -import { Button, Icon, Inspector, InspectorSelectField, LoadingRegion, RangeControl, SegmentedControl, TextField } from '@nodedc/ui-react'; -import { endAtDistance, indexAtDistance, routeLength, canSelectSession, canStartPlanningRoute } from '../../core/missions/planner'; +import { Button, Icon, Inspector, InspectorSelectField, TextField } from '@nodedc/ui-react'; +import { routeLength, canStartPlanningRoute } from '../../core/missions/planner'; import type { useMissionPlanner } from '../../core/missions/useMissionPlanner'; -import type { useRegistrationTest } from '../../core/missions/useRegistrationTest'; -export function PlanningProjectSettings({p,t,mode,setMode,onStart,starting}:{ - p:ReturnType; t:ReturnType; - mode:'scanner'|'recording';setMode:(mode:'scanner'|'recording')=>void;onStart:()=>void;starting:boolean; +export function PlanningProjectSettings({p,onStart,starting}:{ + p:ReturnType; onStart:()=>void; starting:boolean; }) { const disabled=p.busy||starting; const length=routeLength(p.poses); - return ,content:
p.setName(e.target.value)}/>
}, {id:'zone',label:'Эталон',icon:,content:
{p.cursor&&} - {p.source&&{p.source.poses.length.toLocaleString('ru-RU')} положений · {p.source.path_m.toFixed(2)} м} -
}, - {id:'route',label:'Участок эталона',icon:,content:
- {p.source&&!p.sourceChanged?<> - `${n.toFixed(1)} м`} onChange={distance=>{const n=indexAtDistance(p.source!,distance,0,p.source!.poses.length-2);p.setStart(n);if(n>=p.end)p.setEnd(n+1);}}/> - `${n.toFixed(1)} м`} onChange={distance=>p.setEnd(indexAtDistance(p.source!,distance,p.start+1,p.source!.poses.length-1,true))}/> - - + {p.source&&<> + Вся запись · {p.source.path_m.toFixed(2)} м · {p.source.poses.length.toLocaleString('ru-RU')} положений - {length.toFixed(2)} м · {p.poses.length.toLocaleString('ru-RU')} положений - :

Выберите сохранённую запись эталона.

} -
}, - {id:'query',label:'Повторный проход',icon:,content:
- - {mode==='scanner'?

После запуска откроется подключение сканера. Новый проход записывается отдельным проектом.

:<> - ({value:s.id,label:s.label,description:s.id===p.sessionId?'Та же запись · внутренняя проверка':'Сохранённые облако и траектория'}))]}/> - - {t.source&&
- `${n.toFixed(1)} м`} - onChange={n=>{const start=indexAtDistance(t.source!,n,0,t.source!.poses.length-2);t.setStart(start);t.setEnd(endAtDistance(t.source!,start,20));}}/> - `${n.toFixed(1)} м`} onChange={n=>t.setEnd(indexAtDistance(t.source!,n,t.start+1))}/> -
} -
} - {mode==='scanner' - ?'Длина выбранного участка задаёт предел прохода. Ограничения по времени нет; запись сканера не останавливается автоматически. Неподвижная калибровка просматривает весь выбранный маршрут. После калибровки оставайтесь на месте до статуса «Сопровождение»; при отсутствии или неоднозначности совпадения сцена прямо сообщит причину и не начнёт сопровождение.' - :'Участки от 3 до 40 м. Начальная привязка — выбранное место старта и одинаковое направление.'} - - {(p.error||t.error)&&

{p.error||t.error}

}
}, - ]}/>; + ]}/> +
+ {p.source&&!routeReady&&В записи недостаточно перемещения для привязки. Выберите другой эталон.} + + {p.error&&

{p.error}

} +
+ ; } diff --git a/apps/control-station/src/components/observation/SessionOverviewScene.tsx b/apps/control-station/src/components/observation/SessionOverviewScene.tsx index 968c13f..e5baa3c 100644 --- a/apps/control-station/src/components/observation/SessionOverviewScene.tsx +++ b/apps/control-station/src/components/observation/SessionOverviewScene.tsx @@ -2,10 +2,10 @@ import { type ReactNode, useEffect, useRef, useState } from "react"; import { Button, LoadingRegion, RangeControl, SegmentedControl } from "@nodedc/ui-react"; import { createIsolatedRerunHost } from "../rerun/isolatedRerunHost"; import type { RecordedRerunViewer } from "../rerun/recordedRerunFacade"; -import { fetchOverviewSpatial, updateOverviewSpatial, type OverviewSpatialMetadata, type OverviewViewMode } from "../../core/observation/sessionOverviewSpatial"; +import { fetchOverviewSpatial, updateOverviewSpatial, type OverviewSpatialMetadata, type OverviewViewMode, type OverviewRepresentation } from "../../core/observation/sessionOverviewSpatial"; /** A separate, bounded static recording; teardown releases the whole WASM realm. */ -export function SessionOverviewScene({ sourceUrl, toolbar, hideTitle=false }: { sourceUrl: string; toolbar?:ReactNode; hideTitle?:boolean }) { +export function SessionOverviewScene({ sourceUrl, toolbar, hideTitle=false, compareVersions=false }: { sourceUrl: string; toolbar?:ReactNode; hideTitle?:boolean; compareVersions?:boolean }) { const host = useRef(null); const [state, setState] = useState<"loading" | "ready" | "error">("loading"); const [retry, setRetry] = useState(0); @@ -14,6 +14,10 @@ export function SessionOverviewScene({ sourceUrl, toolbar, hideTitle=false }: { const [mode, setMode] = useState("3d"); const [viewError, setViewError] = useState(null); const [visiblePoints, setVisiblePoints] = useState(null); + const [representation, setRepresentation] = useState("original"); + const [appliedRepresentation, setAppliedRepresentation] = useState("original"); + const [updating, setUpdating] = useState(false); + const comparison = metadata?.comparison; const appliedMode = useRef(null); const controller = useRef<{ viewer: RecordedRerunViewer; channel: ReturnType } | null>(null); useEffect(() => { @@ -23,7 +27,11 @@ export function SessionOverviewScene({ sourceUrl, toolbar, hideTitle=false }: { appliedMode.current = null; setState("loading"); setMetadata(null); setCeiling(null); setMode("3d"); setVisiblePoints(null); setViewError(null); - void fetchOverviewSpatial(sourceUrl, abort.signal).then(setMetadata).catch(() => { if (!disposed) setViewError("Параметры среза недоступны."); }); + setRepresentation("original"); setAppliedRepresentation("original"); setUpdating(false); + void fetchOverviewSpatial(sourceUrl, abort.signal).then(data => { + if (disposed) return; + setRepresentation(data.default_representation ?? "original"); setMetadata(data); + }).catch(() => { if (!disposed) { setState("error"); setViewError("Версия облака недоступна."); } }); const runtime = createIsolatedRerunHost(host.current); const timer = window.setTimeout(() => { if (!disposed) { setState("error"); runtime.dispose(); } }, 60_000); void runtime.ready.then(async ({ viewer, mount }) => { @@ -44,29 +52,33 @@ export function SessionOverviewScene({ sourceUrl, toolbar, hideTitle=false }: { useEffect(() => { if (state !== "ready" || !metadata || !controller.current) return; const abort = new AbortController(); + setUpdating(true); const timer = setTimeout(() => { const aspect = Math.max(.1, Math.min(20, (host.current?.clientWidth ?? 1) / Math.max(1, (host.current?.clientHeight ?? 1) - 28))); - void updateOverviewSpatial(sourceUrl, ceiling, appliedMode.current === mode ? null : mode, aspect, abort.signal).then(result => { + void updateOverviewSpatial(sourceUrl, ceiling, appliedMode.current === mode ? null : mode, aspect, abort.signal, + comparison?.generation ?? null, comparison ? representation : "original").then(result => { if (abort.signal.aborted || !controller.current) return; controller.current.channel.send_rrd(result.bytes); - if (result.eye) controller.current.viewer.configure_camera_journal(result.eye, 0); appliedMode.current = mode; setVisiblePoints(result.visiblePoints); setViewError(null); - }).catch(() => { if (!abort.signal.aborted) setViewError("Не удалось обновить вид облака."); }); + setAppliedRepresentation(comparison ? representation : "original"); setUpdating(false); + }).catch(() => { if (!abort.signal.aborted) { setUpdating(false); setViewError("Не удалось обновить вид облака. Показан предыдущий вид."); } }); }, 180); return () => { abort.abort(); clearTimeout(timer); }; - }, [state, metadata, sourceUrl, ceiling, mode, retry]); + }, [state, metadata, sourceUrl, ceiling, mode, retry, comparison, representation]); - const low = metadata?.height_min_m; - const high = metadata?.height_max_m; + const low = comparison?.height_min_m ?? metadata?.height_min_m; + const high = comparison?.height_max_m ?? metadata?.height_max_m; return <>
{!hideTitle&&

Облако и траектория

} + {compareVersions && comparison && } {toolbar}
- -
+ +
{low != null && high != null && high > low && state === "ready" &&
`${value.toFixed(1).replace('.', ',')} м`} formatLimit={value => value.toFixed(1).replace('.', ',')} @@ -74,7 +86,11 @@ export function SessionOverviewScene({ sourceUrl, toolbar, hideTitle=false }: {
} {state === "error" &&
Не удалось открыть облако.
} - {viewError ?
{viewError}
- : {ceiling == null ? "Без среза" : `Высота ≤ ${ceiling.toFixed(1)} м`} · {visiblePoints?.toLocaleString("ru-RU") ?? "—"} точек} + + {updating ? "Обновление · " : comparison ? `${appliedRepresentation === "corrected" ? "Исправленное" : "Исходное"} · ` : ""} + {ceiling == null ? "Без среза" : `Высота ≤ ${ceiling.toFixed(1)} м`} · {visiblePoints?.toLocaleString("ru-RU") ?? "—"} точек + {comparison && <>
Выборка для просмотра. Исходная запись сохранена отдельно.} +
+ {viewError &&
{viewError}
} ; } diff --git a/apps/control-station/src/components/rerun/recordedRerunCameraJournal.ts b/apps/control-station/src/components/rerun/recordedRerunCameraJournal.ts index 80e9c76..d7f6437 100644 --- a/apps/control-station/src/components/rerun/recordedRerunCameraJournal.ts +++ b/apps/control-station/src/components/rerun/recordedRerunCameraJournal.ts @@ -1,244 +1,28 @@ +// Presets and the native snapshot boundary. No DOM-input journal: only Rerun +// knows the actual eye after navigation, auto-fit, interpolation and tracking. export type RecordedRerunCameraEye = { readonly position: readonly [number, number, number]; readonly lookTarget: readonly [number, number, number]; readonly eyeUp: readonly [number, number, number]; }; - -type Vector3 = [number, number, number]; -type DragMode = "rotate" | "pan" | "roll"; - -const ROTATION_RADIANS_PER_POINT = 0.004; -const RERUN_WEB_LINE_SCROLL_POINTS = 8; -const RERUN_ORBITAL_SCROLL_DIVISOR = 200; -const RERUN_WEB_PINCH_SCROLL_DIVISOR = 100; -const RERUN_MIN_ORBIT_DISTANCE = 0.02; -const DOM_WHEEL_DELTA_LINE = 1; -const DOM_WHEEL_DELTA_PAGE = 2; - export const RECORDED_RERUN_ORBITAL_EYE: RecordedRerunCameraEye = { - position: [16, -16, 18], - lookTarget: [0, 0, 0], - eyeUp: [0, 0, 1], + position: [16, -16, 18], lookTarget: [0, 0, 0], eyeUp: [0, 0, 1], }; - export const RECORDED_RERUN_PLAN_EYE: RecordedRerunCameraEye = { - position: [0, 0, 30], - lookTarget: [0, 0, 0], - eyeUp: [0, 1, 0], + position: [0, 0, 30], lookTarget: [0, 0, 0], eyeUp: [0, 1, 0], }; -const vector = (value: readonly [number, number, number]): Vector3 => [...value]; -const add = (a: Vector3, b: Vector3): Vector3 => [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; -const subtract = (a: Vector3, b: Vector3): Vector3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; -const scale = (value: Vector3, factor: number): Vector3 => [value[0] * factor, value[1] * factor, value[2] * factor]; -const dot = (a: Vector3, b: Vector3) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; -const cross = (a: Vector3, b: Vector3): Vector3 => [ - a[1] * b[2] - a[2] * b[1], - a[2] * b[0] - a[0] * b[2], - a[0] * b[1] - a[1] * b[0], -]; -const length = (value: Vector3) => Math.hypot(...value); -const normalize = (value: Vector3, fallback: Vector3): Vector3 => { - const magnitude = length(value); - return magnitude > Number.EPSILON ? scale(value, 1 / magnitude) : fallback; -}; -const clamp = (value: number, minimum: number, maximum: number) => Math.max(minimum, Math.min(maximum, value)); -const rotateAroundAxis = (value: Vector3, rawAxis: Vector3, angle: number): Vector3 => { - const axis = normalize(rawAxis, [0, 0, 1]); - const cosine = Math.cos(angle); - const sine = Math.sin(angle); - return add( - add(scale(value, cosine), scale(cross(axis, value), sine)), - scale(axis, dot(axis, value) * (1 - cosine)), - ); -}; - -function cloneEye(value: RecordedRerunCameraEye): { - position: Vector3; - lookTarget: Vector3; - eyeUp: Vector3; -} { - return { - position: vector(value.position), - lookTarget: vector(value.lookTarget), - eyeUp: vector(value.eyeUp), - }; -} - -/** - * Mirrors Rerun 0.36.3's orbital eye math while the native viewer remains the - * renderer. Rerun writes operator navigation into its active blueprint clone; - * the WebViewer API cannot read that clone. Keeping the same three eye vectors - * here lets a later layer blueprint activate with the exact operator pose. - */ -export function createRecordedRerunCameraJournal( - canvas: HTMLCanvasElement, - scope: Window & typeof globalThis, -) { - let eye = cloneEye(RECORDED_RERUN_ORBITAL_EYE); - let spatialViewportStart = 0; - let pointerId: number | null = null; - let dragMode: DragMode | null = null; - let previousPointer: readonly [number, number] | null = null; - let latestPointer: readonly [number, number] | null = null; - let maxOrbitalRadius = 1.0e17; - - const isSpatialPoint = (event: MouseEvent) => { - const rect = canvas.getBoundingClientRect(); - return rect.width > 0 && (event.clientX - rect.left) / rect.width >= spatialViewportStart; - }; - const forward = () => normalize(subtract(eye.lookTarget, eye.position), [0, 1, 0]); - const usableUp = () => { - const fwd = forward(); - const fallbackRight: Vector3 = Math.abs(dot(fwd, [0, 0, 1])) > 0.9999 - ? [1, 0, 0] - : cross(fwd, [0, 0, 1]); - const fallback = normalize(cross(fwd, fallbackRight), [0, 0, 1]); - const candidate = normalize(eye.eyeUp, fallback); - return Math.abs(dot(candidate, fwd)) > 0.9999 ? fallback : candidate; - }; - const orbitRadius = () => length(subtract(eye.position, eye.lookTarget)); - - const rotate = (deltaX: number, deltaY: number) => { - const radius = orbitRadius(); - const up = usableUp(); - let fwd = forward(); - const oldPitch = Math.asin(clamp(dot(fwd, up), -1, 1)); - fwd = normalize( - rotateAroundAxis(fwd, up, -ROTATION_RADIANS_PER_POINT * deltaX), - fwd, - ); - const right = normalize(cross(fwd, up), [1, 0, 0]); - const maxPitch = 0.99 * Math.PI / 2; - const nextPitch = clamp( - oldPitch - ROTATION_RADIANS_PER_POINT * deltaY, - -maxPitch, - maxPitch, - ); - fwd = normalize(rotateAroundAxis(fwd, right, nextPitch - oldPitch), fwd); - eye.position = subtract(eye.lookTarget, scale(fwd, radius)); - }; - - const pan = (deltaX: number, deltaY: number) => { - const fwd = forward(); - const right = normalize(cross(fwd, usableUp()), [1, 0, 0]); - const screenUp = normalize(cross(right, fwd), [0, 0, 1]); - const speed = 0.001 * orbitRadius(); - const translation = add(scale(right, -deltaX * speed), scale(screenUp, deltaY * speed)); - eye.position = add(eye.position, translation); - eye.lookTarget = add(eye.lookTarget, translation); - }; - - const roll = (event: PointerEvent, deltaX: number, deltaY: number) => { - const rect = canvas.getBoundingClientRect(); - const left = rect.left + rect.width * spatialViewportStart; - const centerX = left + (rect.right - left) / 2; - const centerY = rect.top + rect.height / 2; - const relativeX = event.clientX - centerX; - const relativeY = event.clientY - centerY; - const divisor = relativeX * relativeX + relativeY * relativeY; - if (divisor <= Number.EPSILON) return; - const angle = (-deltaY * relativeX + deltaX * relativeY) / divisor; - eye.eyeUp = normalize(rotateAroundAxis(eye.eyeUp, scale(forward(), -1), angle), eye.eyeUp); - }; - - const pointerDown = (event: PointerEvent) => { - latestPointer = [event.clientX, event.clientY]; - if (!isSpatialPoint(event)) return; - pointerId = event.pointerId; - previousPointer = [event.clientX, event.clientY]; - dragMode = event.button === 2 ? "pan" : event.button === 1 || event.altKey ? "roll" : "rotate"; - }; - const pointerMove = (event: PointerEvent) => { - latestPointer = [event.clientX, event.clientY]; - if (event.pointerId !== pointerId || !previousPointer || !dragMode) return; - const deltaX = event.clientX - previousPointer[0]; - const deltaY = event.clientY - previousPointer[1]; - previousPointer = [event.clientX, event.clientY]; - if (dragMode === "rotate") rotate(deltaX, deltaY); - else if (dragMode === "pan") pan(deltaX, deltaY); - else roll(event, deltaX, deltaY); - }; - const pointerEnd = (event: PointerEvent) => { - if (event.pointerId !== pointerId) return; - pointerMove(event); - pointerId = null; - previousPointer = null; - dragMode = null; - }; - const wheel = (event: WheelEvent) => { - const rect = canvas.getBoundingClientRect(); - const wheelX = event.clientX >= rect.left && event.clientX <= rect.right - ? event.clientX - : latestPointer?.[0]; - if (wheelX === undefined || rect.width <= 0) return; - if ((wheelX - rect.left) / rect.width < spatialViewportStart) return; - const unitMultiplier = event.deltaMode === DOM_WHEEL_DELTA_LINE - ? RERUN_WEB_LINE_SCROLL_POINTS - : event.deltaMode === DOM_WHEEL_DELTA_PAGE - ? rect.height - : 1; - const scrollPoints = (event.deltaX + event.deltaY) * unitMultiplier; - // eframe negates the DOM delta before Rerun applies - // radius / exp(smooth_scroll_delta / 200). Over a smoothed gesture the - // exponent products collapse to the raw accumulated DOM delta below. - const divisor = event.ctrlKey - ? RERUN_WEB_PINCH_SCROLL_DIVISOR - : RERUN_ORBITAL_SCROLL_DIVISOR; - const radiusFactor = Math.exp(scrollPoints / divisor); - const offset = subtract(eye.position, eye.lookTarget); - const radius = length(offset); - // Keep the same orbital bounds as Rerun 0.36.3. An eye already outside - // the scene-derived cap is allowed to stay there and is never snapped in. - const nextRadius = clamp( - radius * radiusFactor, - RERUN_MIN_ORBIT_DISTANCE, - Math.max(radius, maxOrbitalRadius), - ); - eye.position = add( - eye.lookTarget, - scale(offset, nextRadius / Math.max(radius, Number.EPSILON)), - ); - }; - - canvas.addEventListener("pointerdown", pointerDown, true); - scope.addEventListener("pointermove", pointerMove, true); - scope.addEventListener("pointerup", pointerEnd, true); - scope.addEventListener("pointercancel", pointerEnd, true); - scope.addEventListener("wheel", wheel, { capture: true, passive: true }); - - return { - current(): RecordedRerunCameraEye { - return { - position: [...eye.position], - lookTarget: [...eye.lookTarget], - eyeUp: [...eye.eyeUp], - }; - }, - configure(nextEye: RecordedRerunCameraEye, nextSpatialViewportStart: number) { - eye = cloneEye(nextEye); - spatialViewportStart = clamp(nextSpatialViewportStart, 0, 0.9); - pointerId = null; - dragMode = null; - previousPointer = null; - }, - setSpatialViewportStart(nextSpatialViewportStart: number) { - spatialViewportStart = clamp(nextSpatialViewportStart, 0, 0.9); - }, - setMaxOrbitalRadius(nextMaxOrbitalRadius: number) { - if ( - Number.isFinite(nextMaxOrbitalRadius) && - nextMaxOrbitalRadius >= RERUN_MIN_ORBIT_DISTANCE - ) { - maxOrbitalRadius = nextMaxOrbitalRadius; - } - }, - dispose() { - canvas.removeEventListener("pointerdown", pointerDown, true); - scope.removeEventListener("pointermove", pointerMove, true); - scope.removeEventListener("pointerup", pointerEnd, true); - scope.removeEventListener("pointercancel", pointerEnd, true); - scope.removeEventListener("wheel", wheel, true); - }, +export function readNativeRerunCameraEye(value: unknown): RecordedRerunCameraEye | null { + if (value == null) return null; // No 3D frame rendered yet. + if (typeof value !== "object") throw new Error("Invalid native Rerun camera snapshot"); + const eye = value as Record; + const vector = (key: string): [number, number, number] => { + const item = eye[key]; + if (!Array.isArray(item) || item.length !== 3 || + !item.every(component => typeof component === "number" && Number.isFinite(component))) { + throw new Error("Invalid native Rerun camera vector"); + } + return [item[0], item[1], item[2]]; }; + return { position: vector("position"), lookTarget: vector("lookTarget"), eyeUp: vector("eyeUp") }; } diff --git a/apps/control-station/src/components/rerun/recordedRerunFacade.ts b/apps/control-station/src/components/rerun/recordedRerunFacade.ts index 08da524..e42e7d5 100644 --- a/apps/control-station/src/components/rerun/recordedRerunFacade.ts +++ b/apps/control-station/src/components/rerun/recordedRerunFacade.ts @@ -11,10 +11,7 @@ export type RecordedRerunViewer = Pick & { open_channel: (name?: string) => Channel; - configure_camera_journal: (eye: RecordedRerunCameraEye, spatialViewportStart: number) => void; - get_camera_eye: () => RecordedRerunCameraEye; - set_camera_viewport_start: (spatialViewportStart: number) => void; - set_camera_max_orbital_radius: (maxOrbitalRadius: number) => void; + get_camera_eye: () => RecordedRerunCameraEye | null; }; /** Parent-owned values only. No SDK Promise or foreign prototype escapes. */ @@ -90,16 +87,7 @@ export function createRecordedRerunFacade(invoke: RerunInvoke | null) { set_active_timeline: (...args) => command("set_active_timeline", args), set_current_time: (...args) => command("set_current_time", args), set_playing: (...args) => command("set_playing", args), - configure_camera_journal: (eye, spatialViewportStart) => command( - "configure-camera-journal", [eye, spatialViewportStart], - ), get_camera_eye: () => command("get-camera-eye"), - set_camera_viewport_start: (spatialViewportStart) => command( - "set-camera-viewport-start", [spatialViewportStart], - ), - set_camera_max_orbital_radius: (maxOrbitalRadius) => command( - "set-camera-max-orbital-radius", [maxOrbitalRadius], - ), }; return { facade, dispose, notify }; } diff --git a/apps/control-station/src/components/rerun/recordedRerunOwner.ts b/apps/control-station/src/components/rerun/recordedRerunOwner.ts index c4e6edf..b42a2c0 100644 --- a/apps/control-station/src/components/rerun/recordedRerunOwner.ts +++ b/apps/control-station/src/components/rerun/recordedRerunOwner.ts @@ -1,6 +1,6 @@ import type { WebViewer } from "@rerun-io/web-viewer"; import type { RerunFrameApi, RerunNotify } from "./recordedRerunProtocol"; -import { createRecordedRerunCameraJournal } from "./recordedRerunCameraJournal"; +import { readNativeRerunCameraEye } from "./recordedRerunCameraJournal"; /** Lives entirely inside the disposable iframe, including pending SDK starts. */ export function createRecordedRerunOwner(create: () => WebViewer, mount: HTMLElement): RerunFrameApi { @@ -8,7 +8,6 @@ export function createRecordedRerunOwner(create: () => WebViewer, mount: HTMLEle let notify: RerunNotify | null = null; const subscriptions = new Map void>(); const channels = new Map>(); - let cameraJournal: ReturnType | null = null; const send = (message: object) => notify?.(JSON.stringify(message)); const stop = () => { notify = null; @@ -20,8 +19,6 @@ export function createRecordedRerunOwner(create: () => WebViewer, mount: HTMLEle try { channel.close(); } catch { /* Other channels must still close. */ } } channels.clear(); - cameraJournal?.dispose(); - cameraJournal = null; const current = native; native = null; try { current?.stop(); } catch { /* Realm teardown remains authoritative. */ } @@ -34,13 +31,6 @@ export function createRecordedRerunOwner(create: () => WebViewer, mount: HTMLEle const starting = required(); try { await starting.start(args[0], mount, args[1]); - if (native === starting && starting.canvas) { - cameraJournal?.dispose(); - cameraJournal = createRecordedRerunCameraJournal( - starting.canvas, - window as Window & typeof globalThis, - ); - } if (native === starting) send({ type: "started", id }); } catch (error) { if (native === starting) send({ type: "start-failed", id, message: String(error) }); @@ -87,10 +77,9 @@ export function createRecordedRerunOwner(create: () => WebViewer, mount: HTMLEle channels.delete(id); } break; - case "configure-camera-journal": cameraJournal?.configure(args[0], args[1]); break; - case "get-camera-eye": result = cameraJournal?.current(); break; - case "set-camera-viewport-start": cameraJournal?.setSpatialViewportStart(args[0]); break; - case "set-camera-max-orbital-radius": cameraJournal?.setMaxOrbitalRadius(args[0]); break; + case "get-camera-eye": result = readNativeRerunCameraEye( + (required() as WebViewer & { get_camera_eye?(): unknown }).get_camera_eye?.(), + ); break; case "open": required().open(args[0]); break; case "close": required().close(args[0]); break; case "override_panel_state": required().override_panel_state(args[0], args[1]); break; diff --git a/apps/control-station/src/components/rerun/useRecordedPointDisplay.ts b/apps/control-station/src/components/rerun/useRecordedPointDisplay.ts new file mode 100644 index 0000000..37d1d01 --- /dev/null +++ b/apps/control-station/src/components/rerun/useRecordedPointDisplay.ts @@ -0,0 +1,66 @@ +import {useEffect, useRef, useState} from 'react'; +import {pointDisplayKey, streamRecordedPointDisplay, type PointDisplaySettings} from '../../core/observation/recordedPointDisplay'; +import type {RecordedPointColorLoadState} from '../RerunViewport'; + +type Owner = {send: (bytes: Uint8Array) => void; identity: {applicationId: string; recordingId: string}}; + +export function useRecordedPointDisplay({endpoint, settings, revision, ready, getOwner, onLoad, sourceGeneration}: { + endpoint: string | null; settings: PointDisplaySettings | undefined; revision: number; ready: boolean; + getOwner: () => Owner | null; onLoad?: (state: RecordedPointColorLoadState) => void; + sourceGeneration?: string; +}) { + const [applied, setApplied] = useState<{endpoint: string; revision: number; key: string; bank: string; sourceGeneration?: string} | null>(null); + const ownerRef = useRef(getOwner); ownerRef.current = getOwner; + const loadRef = useRef(onLoad); loadRef.current = onLoad; + const failed = useRef(false); + const [retry, setRetry] = useState(0); + const key = settings ? pointDisplayKey(settings) : ''; + const percent = settings?.pointDecimationPercent ?? 0; + const sameSource = applied?.endpoint === endpoint && applied?.revision === revision && applied?.sourceGeneration === sourceGeneration; + const matched = sameSource && applied?.key === key; + useEffect(() => { + // A new settings interaction retries a failed request, but accumulation or + // size edits must not cancel an already-running preparation of the same key. + if (failed.current) { failed.current = false; setRetry(value => value + 1); } + }, [settings]); + useEffect(() => { + if (!endpoint || !settings || !ready) return; + if (percent === 0 || percent === 100) { + loadRef.current?.({phase: 'ready', receivedBytes: 0, totalBytes: 0, + progress: 1, message: percent === 100 ? 'Точки скрыты.' : 'Все точки.'}); + return; + } + if (matched) { + loadRef.current?.({phase: 'ready', receivedBytes: 0, totalBytes: 0, + progress: 1, message: 'Прореженное облако готово.'}); + return; + } + const owner = ownerRef.current(); + if (!owner) return; + const abort = new AbortController(); + failed.current = false; + // Never append another generation at the same temporal entity path: an + // accumulated range would otherwise draw older samples as well. + const bank = crypto.randomUUID().replaceAll('-', ''); + loadRef.current?.({phase: 'loading', receivedBytes: 0, totalBytes: null, progress: null, + message: 'Готовим прореженное облако. Текущий вид сохранён.'}); + void streamRecordedPointDisplay(endpoint, settings, owner.identity, bank, abort.signal, chunk => { + if (!abort.signal.aborted) owner.send(chunk); + }, undefined, sourceGeneration).then(bytes => { + if (abort.signal.aborted) return; + setApplied({endpoint, revision, key, bank, sourceGeneration}); + loadRef.current?.({phase: 'ready', receivedBytes: bytes, totalBytes: bytes, progress: 1, + message: 'Прореженное облако готово.'}); + }).catch(() => { + if (!abort.signal.aborted) { + failed.current = true; + loadRef.current?.({phase: 'error', receivedBytes: 0, totalBytes: null, + progress: null, message: 'Прореживание не применено. Предыдущий вид сохранён. Измените значение или закройте настройки для повтора.'}); + } + }); + return () => abort.abort(); + }, [endpoint, key, revision, ready, retry, sourceGeneration]); + return {ready: !endpoint || percent === 0 || percent === 100 || matched, + bank: percent > 0 && percent < 100 && sameSource ? applied?.bank ?? null : null, + hidePoints: percent === 100}; +} diff --git a/apps/control-station/src/core/missions/useMissionPlanner.ts b/apps/control-station/src/core/missions/useMissionPlanner.ts index 4b49d27..2d34822 100644 --- a/apps/control-station/src/core/missions/useMissionPlanner.ts +++ b/apps/control-station/src/core/missions/useMissionPlanner.ts @@ -1,8 +1,7 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { canSelectSession, endAtDistance, plannerBase, plannerRequest, selectedPoses, validatePlanningSource, type Direction, type Draft, type PlanningSource, type RouteCheck, type SessionOption } from "./planner"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { canSelectSession, plannerBase, plannerRequest, selectedPoses, validatePlanningSource, type Direction, type Draft, type PlanningSource, type RouteCheck, type SessionOption } from "./planner"; export function useMissionPlanner() { - const initializeRoute = useRef(true); const [sessions, setSessions] = useState([]); const [cursor, setCursor] = useState(null); const [drafts, setDrafts] = useState([]); @@ -10,8 +9,6 @@ export function useMissionPlanner() { const [name, setName] = useState(""); const [sessionId, setSessionId] = useState(""); const [source, setSource] = useState(null); - const [start, setStart] = useState(0); - const [end, setEnd] = useState(1); const [direction, setDirection] = useState("forward"); const [error, setError] = useState(null); const [sourceError, setSourceError] = useState(null); @@ -19,12 +16,13 @@ export function useMissionPlanner() { const [catalogBusy, setCatalogBusy] = useState(true); const [loadVersion, setLoadVersion] = useState(0); const [sourceVersion, setSourceVersion] = useState(0); + const [pinnedGeneration, setPinnedGeneration] = useState(null); const [check, setCheck] = useState(null); useEffect(() => { const abort = new AbortController(); setCatalogBusy(true); setError(null); void Promise.all([ - plannerRequest<{ items: SessionOption[]; next_cursor: string | null }>("/api/v1/observation-sessions?limit=100&pagination=cursor-v1", { signal: abort.signal }), + plannerRequest<{ items: SessionOption[]; next_cursor: string | null }>("/api/v1/observation-sessions?scope=standalone&limit=100&pagination=cursor-v1", { signal: abort.signal }), plannerRequest<{ items: Draft[] }>(`${plannerBase}/drafts`, { signal: abort.signal }), ]).then(([catalog, archive]) => { if (!abort.signal.aborted) { setSessions(catalog.items); setCursor(catalog.next_cursor); setDrafts(archive.items); } }) .catch(reason => { if (!abort.signal.aborted) setError(reason.message); }) @@ -34,26 +32,29 @@ export function useMissionPlanner() { useEffect(() => { const abort = new AbortController(); setSource(null); setSourceError(null); - if (sessionId) void plannerRequest(`${plannerBase}/sources/${encodeURIComponent(sessionId)}`, { signal: abort.signal }) - .then(data => { if (!abort.signal.aborted) { const next = validatePlanningSource(data, sessionId); setSource(next); if (initializeRoute.current) { setStart(0); setEnd(endAtDistance(next, 0, 30)); initializeRoute.current = false; } } }) + if (sessionId) void plannerRequest(`${plannerBase}/sources/${encodeURIComponent(sessionId)}${pinnedGeneration ? `?generation=${encodeURIComponent(pinnedGeneration)}` : ""}`, { signal: abort.signal }) + .then(data => { if (!abort.signal.aborted) setSource(validatePlanningSource(data, sessionId)); }) .catch(reason => { if (!abort.signal.aborted) setSourceError(reason.message); }); return () => abort.abort(); - }, [sessionId, sourceVersion]); + }, [sessionId, sourceVersion, pinnedGeneration]); const currentSource = source?.session_id === sessionId ? source : null; + // New executions always own the complete reference. No independent interval + // state can survive a source change or collapse on same-source reselection. + const start = 0, end = currentSource ? currentSource.poses.length - 1 : 0; const sourceChanged = !!(saved && currentSource && saved.zone.session_id === sessionId && saved.zone.generation !== currentSource.generation); const poses = useMemo(() => selectedPoses(currentSource, start, end, direction), [currentSource, start, end, direction]); const dirty = !saved || name.trim() !== saved.name || sessionId !== saved.zone.session_id || sourceChanged || start !== saved.route.start_index || end !== saved.route.end_index || direction !== saved.route.direction; const ready = !!currentSource && !sourceChanged && poses.length > 1 && !!name.trim(); - const chooseSource = (id: string) => { initializeRoute.current = true; setSessionId(id); setStart(0); setEnd(1); setCheck(null); }; + const chooseSource = (id: string) => { setPinnedGeneration(null); setSessionId(id); setSourceVersion(n => n + 1); setCheck(null); }; const newDraft = () => { setSaved(null); setName(""); chooseSource(""); setDirection("forward"); setError(null); }; const openDraft = async (id: string) => { setBusy(true); setError(null); try { const next = await plannerRequest(`${plannerBase}/drafts/${id}`); - initializeRoute.current = false; setSaved(next); setName(next.name); setSessionId(next.zone.session_id); - setStart(next.route.start_index); setEnd(next.route.end_index); setDirection(next.route.direction); setCheck(null); + setSaved(next); setName(next.name); setSessionId(next.zone.session_id); setPinnedGeneration(next.zone.generation); + setDirection(next.route.direction); setCheck(null); setSourceVersion(n => n + 1); } catch (reason) { setError((reason as Error).message); } finally { setBusy(false); } }; @@ -63,7 +64,7 @@ export function useMissionPlanner() { try { const next = await plannerRequest(`${plannerBase}/drafts`, { method: "POST", body: JSON.stringify({ id: saved?.id ?? null, revision: saved?.revision ?? 0, name: name.trim(), session_id: sessionId, - generation: currentSource.generation, start_index: start, end_index: end, direction, + generation: currentSource.generation, whole_recording: true, direction, }) }); setSaved(next); setDrafts(items => [next, ...items.filter(item => item.id !== next.id)]); return next; @@ -79,11 +80,11 @@ export function useMissionPlanner() { if (!cursor) return; setCatalogBusy(true); try { - const page = await plannerRequest<{ items: SessionOption[]; next_cursor: string | null }>(`/api/v1/observation-sessions?limit=100&pagination=cursor-v1&cursor=${encodeURIComponent(cursor)}`); + const page = await plannerRequest<{ items: SessionOption[]; next_cursor: string | null }>(`/api/v1/observation-sessions?scope=standalone&limit=100&pagination=cursor-v1&cursor=${encodeURIComponent(cursor)}`); setSessions(items => [...items, ...page.items.filter(item => !items.some(old => old.id === item.id))]); setCursor(page.next_cursor); } catch (reason) { setError((reason as Error).message); } finally { setCatalogBusy(false); } }; - return { sessions, drafts, saved, name, setName, sessionId, chooseSource, source: currentSource, start, setStart, end, setEnd, + return { sessions, drafts, saved, name, setName, sessionId, chooseSource, source: currentSource, direction, setDirection, error, sourceError, sourceChanged, busy, catalogBusy, cursor, loadMore, dirty, ready, poses, check, save, openDraft, newDraft, runCheck, retrySource: () => setSourceVersion(n => n + 1), refresh: useCallback(() => setLoadVersion(n => n + 1), []), options: [{ value: "", label: "Выберите сохранённую запись" }, ...sessions.map(item => ({ value: item.id, label: item.label, diff --git a/apps/control-station/src/core/missions/useRegistrationTest.ts b/apps/control-station/src/core/missions/useRegistrationTest.ts index fbfa830..4129c0c 100644 --- a/apps/control-station/src/core/missions/useRegistrationTest.ts +++ b/apps/control-station/src/core/missions/useRegistrationTest.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { endAtDistance, plannerBase, plannerRequest, validatePlanningSource, type Draft, type PlanningSource } from "./planner"; +import { endAtDistance, plannerBase, plannerRequest, validatePlanningSource, type Draft, type PlanningSource, type SessionOption } from "./planner"; export interface RegistrationReport { id: string; state: "queued" | "running" | "ready" | "error"; message?: string; progress_label?: string; @@ -15,6 +15,25 @@ export function useRegistrationTest(draft: Draft | null) { const [start, setStart] = useState(0), [end, setEnd] = useState(1); const [error, setError] = useState(null); const [loading, setLoading] = useState(false), [submitting, setSubmitting] = useState(false); + const [sessions,setSessions]=useState([]), [cursor,setCursor]=useState(null); + const [catalogBusy,setCatalogBusy]=useState(false); + // Repeat recordings belong to the planner, including its own captured passes; + // they are not restricted to the independent-reference catalog. + useEffect(()=>{ + const abort=new AbortController();setCatalogBusy(true); + void plannerRequest<{items:SessionOption[];next_cursor:string|null}>("/api/v1/observation-sessions?scope=source&limit=100&pagination=cursor-v1",{signal:abort.signal}) + .then(page=>{if(!abort.signal.aborted){setSessions(page.items);setCursor(page.next_cursor);}}) + .catch(e=>{if(!abort.signal.aborted)setError(e.message);}) + .finally(()=>{if(!abort.signal.aborted)setCatalogBusy(false);}); + return()=>abort.abort(); + },[]); + const loadMore=async()=>{ + if(!cursor||catalogBusy)return;setCatalogBusy(true); + try{ + const page=await plannerRequest<{items:SessionOption[];next_cursor:string|null}>(`/api/v1/observation-sessions?scope=source&limit=100&pagination=cursor-v1&cursor=${encodeURIComponent(cursor)}`); + setSessions(items=>[...items,...page.items.filter(item=>!items.some(old=>old.id===item.id))]);setCursor(page.next_cursor); + }catch(e){setError((e as Error).message);}finally{setCatalogBusy(false);} + }; useEffect(() => { const abort = new AbortController(); setSource(null); setError(null); setLoading(!!sessionId); if (sessionId) void plannerRequest(`${plannerBase}/sources/${encodeURIComponent(sessionId)}`, { signal: abort.signal }) @@ -35,5 +54,5 @@ export function useRegistrationTest(draft: Draft | null) { return data; } catch (e) { setError((e as Error).message); } finally { setSubmitting(false); } }; - return { sessionId, setSessionId, source, start, setStart, end, setEnd, error, busy, loading, length, run }; + return { sessionId, setSessionId, source, start, setStart, end, setEnd, error, busy, loading, length, run, sessions, cursor, catalogBusy, loadMore }; } diff --git a/apps/control-station/src/core/observation/recordedPointDisplay.ts b/apps/control-station/src/core/observation/recordedPointDisplay.ts new file mode 100644 index 0000000..5cabd09 --- /dev/null +++ b/apps/control-station/src/core/observation/recordedPointDisplay.ts @@ -0,0 +1,68 @@ +import type {SceneSettings} from '../../sceneSettings'; + +export type PointDisplaySettings = Pick; +export function pointDisplayKey(settings: PointDisplaySettings): string { + return JSON.stringify([settings.pointDecimationPercent ?? 0, settings.colorMode, settings.palette, + settings.palette === 'custom' || settings.colorMode === 'class' ? settings.customColor.toLowerCase() : '']); +} + +/** Feed bounded HTTP chunks to the existing native receiver; no whole-cloud JS Blob. */ +export async function streamRecordedPointDisplay( + blueprintUrl: string, settings: PointDisplaySettings, + identity: {applicationId: string; recordingId: string}, displayBank: string, + signal: AbortSignal, send: (chunk: Uint8Array) => void, + fetcher: typeof fetch = fetch, + sourceGeneration?: string, +): Promise { + const endpoint = new URL(blueprintUrl, window.location.origin); + const percent = settings.pointDecimationPercent ?? 0; + if (endpoint.origin !== window.location.origin || endpoint.search || endpoint.hash + || !/^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/blueprint\.rrd$/.test(endpoint.pathname) + || !Number.isFinite(percent) || percent <= 0 || percent >= 100 + || !/^[a-f0-9]{32}$/.test(displayBank)) throw new Error('Некорректный запрос прореживания'); + endpoint.pathname = endpoint.pathname.replace('/blueprint.rrd', '/point-display.rrd'); + const response = await fetcher(endpoint.href, {method: 'POST', signal, credentials: 'same-origin', + headers: {'Content-Type': 'application/json', Accept: 'application/vnd.nodedc.point-display-stream'}, + body: JSON.stringify({application_id: identity.applicationId, recording_id: identity.recordingId, + color_mode: settings.colorMode, palette: settings.palette, custom_color: settings.customColor, + point_decimation_percent: percent, display_bank: displayBank, + ...(sourceGeneration ? {source_generation: sourceGeneration} : {})})}); + if (!response.ok || !response.headers.get('Content-Type')?.startsWith('application/vnd.nodedc.point-display-stream') || !response.body) { + throw new Error('Не удалось подготовить прореженное облако'); + } + const reader = response.body.getReader(); + let bytes = 0; + let pending = new Uint8Array(0); + let admitted = false; + let complete = false; + try { + while (true) { + const {done, value} = await reader.read(); + if (signal.aborted) throw new DOMException('Aborted', 'AbortError'); + if (done) break; + bytes += value.byteLength; + const combined = new Uint8Array(pending.length + value.length); + combined.set(pending); combined.set(value, pending.length); pending = combined; + if (!admitted) { + if (pending.length < 4) continue; + if (String.fromCharCode(...pending.subarray(0, 4)) !== 'NPD1') throw new Error('Некорректный поток облака'); + pending = pending.slice(4); admitted = true; + } + while (pending.length >= 4 && !complete) { + const length = new DataView(pending.buffer, pending.byteOffset, 4).getUint32(0, true); + if (length === 0) { pending = pending.slice(4); complete = true; break; } + if (length < 4 || length > 64 * 1024 * 1024) throw new Error('Некорректный размер пакета облака'); + if (pending.length < length + 4) break; + const payload = pending.slice(4, length + 4); + if (String.fromCharCode(...payload.subarray(0, 4)) !== 'RRF2') throw new Error('Некорректный пакет облака'); + send(payload); pending = pending.slice(length + 4); + } + if (complete && pending.length) throw new Error('Лишние данные после завершения облака'); + } + if (!complete || pending.length) throw new Error('Поток облака не завершён'); + return bytes; + } finally { + await reader.cancel().catch(() => {}); + reader.releaseLock(); + } +} diff --git a/apps/control-station/src/core/observation/sessionArchive.ts b/apps/control-station/src/core/observation/sessionArchive.ts index 78ab5dc..b920bb8 100644 --- a/apps/control-station/src/core/observation/sessionArchive.ts +++ b/apps/control-station/src/core/observation/sessionArchive.ts @@ -23,7 +23,7 @@ export type ObservationSessionStatus = | "interrupted" | "failed"; -export type ObservationSessionScope = "all" | "source" | "laboratory"; +export type ObservationSessionScope = "all" | "source" | "standalone" | "laboratory"; export interface ObservationLabInstance { labId: string; @@ -79,6 +79,7 @@ export interface ObservationSessionReplayLaunch { seekable: true; byteLength: number; sha256: string; + mapGeneration?: string; playback: { speed: number; loop: boolean; @@ -237,6 +238,7 @@ const REPLAY_LAUNCH_KEYS = new Set([ "seekable", "byte_length", "sha256", + "map_generation", "playback", "media_sources", ]); @@ -787,6 +789,9 @@ export function decodeObservationSessionReplay( if (typeof launch.sha256 !== "string" || !SHA256.test(launch.sha256)) { throw new ObservationSessionContractError("Descriptor записи не содержит SHA-256."); } + if (launch.map_generation !== undefined && (typeof launch.map_generation !== "string" || !SHA256.test(launch.map_generation))) { + throw new ObservationSessionContractError("Descriptor содержит некорректную версию исправления."); + } const viewerSourceUrl = requireString( launch.viewer_source_url, "launch.viewer_source_url", @@ -845,6 +850,7 @@ export function decodeObservationSessionReplay( integer: true, }), sha256: launch.sha256, + ...(launch.map_generation === undefined ? {} : { mapGeneration: launch.map_generation as string }), playback: { speed, loop: launch.playback.loop }, mediaSources, }; diff --git a/apps/control-station/src/core/observation/sessionDisplayProfile.ts b/apps/control-station/src/core/observation/sessionDisplayProfile.ts new file mode 100644 index 0000000..ad16969 --- /dev/null +++ b/apps/control-station/src/core/observation/sessionDisplayProfile.ts @@ -0,0 +1,54 @@ +import {defaultSceneSettings, MAX_ACCUMULATION_SECONDS, type SceneSettings} from '../../sceneSettings'; + +const schema = 'missioncore.session-display-profile/v1'; +const fields = { + projection: 'projection', pointSize: 'point_size', pointDecimationPercent: 'point_decimation_percent', + accumulationMaxSeconds: 'accumulation_max_seconds', accumulationSeconds: 'accumulation_seconds', + colorMode: 'color_mode', palette: 'palette', customColor: 'custom_color', + showPoints: 'show_points', showTrajectory: 'show_trajectory', showGrid: 'show_grid', + showLabels: 'show_labels', showCameraFrustums: 'show_camera_frustums', +} as const; + +function endpoint(sessionId: string) { + if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(sessionId)) throw new Error('Некорректная запись'); + return `/api/v1/observation-sessions/${encodeURIComponent(sessionId)}/display-profile`; +} + +export function encodeSessionDisplay(settings: SceneSettings) { + return Object.fromEntries(Object.entries(fields).map(([key, wire]) => + [wire, settings[key as keyof SceneSettings] ?? defaultSceneSettings[key as keyof SceneSettings]])); +} + +export function decodeSessionDisplay(value: unknown, sessionId: string): SceneSettings { + const document = value as {schema_version?: string; session_id?: string; scene_settings?: Record}; + if (!document || document.schema_version !== schema || document.session_id !== sessionId || !document.scene_settings) { + throw new Error('Настройки принадлежат другой записи или имеют неизвестный формат'); + } + const s = Object.fromEntries(Object.entries(fields).map(([key, wire]) => [key, document.scene_settings![wire]])) as unknown as SceneSettings; + for (const key of ['pointSize', 'accumulationSeconds', 'accumulationMaxSeconds', 'pointDecimationPercent'] as const) { + if (typeof s[key] !== 'number' || !Number.isFinite(s[key])) throw new Error('Некорректное значение настройки'); + } + if (s.pointSize < 0.1 || s.pointSize > 32 || s.accumulationSeconds < 0 + || s.accumulationMaxSeconds! < 1 || s.pointDecimationPercent! < 0 || s.pointDecimationPercent! > 100 + || !['3d','2d','map'].includes(s.projection) || !['intensity','height','distance','rgb','class'].includes(s.colorMode) + || !['turbo','viridis','plasma','grayscale','custom'].includes(s.palette) || !/^#[a-f0-9]{6}$/i.test(s.customColor) + || ['showPoints','showTrajectory','showGrid','showLabels','showCameraFrustums'].some(key => typeof s[key as keyof SceneSettings] !== 'boolean')) { + throw new Error('Некорректные настройки отображения'); + } + return {...s, accumulationMaxSeconds: Math.max(s.accumulationSeconds, s.accumulationMaxSeconds!)}; +} + +export async function loadSessionDisplay(sessionId: string, signal?: AbortSignal): Promise { + const response = await fetch(endpoint(sessionId), {signal, cache: 'no-store'}); + if (!response.ok) throw new Error('Не удалось прочитать настройки записи'); + const document = await response.json(); + return document === null ? {...defaultSceneSettings, accumulationMaxSeconds: MAX_ACCUMULATION_SECONDS} + : decodeSessionDisplay(document, sessionId); +} + +export async function saveSessionDisplay(sessionId: string, settings: SceneSettings): Promise { + const response = await fetch(endpoint(sessionId), {method: 'PUT', headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({scene_settings: encodeSessionDisplay(settings)})}); + if (!response.ok) throw new Error('Настройки записи не сохранены. Текущий вид оставлен без изменений.'); + decodeSessionDisplay(await response.json(), sessionId); +} diff --git a/apps/control-station/src/core/observation/sessionOverviewSpatial.ts b/apps/control-station/src/core/observation/sessionOverviewSpatial.ts index 6ca1b12..145c2cd 100644 --- a/apps/control-station/src/core/observation/sessionOverviewSpatial.ts +++ b/apps/control-station/src/core/observation/sessionOverviewSpatial.ts @@ -1,5 +1,14 @@ export type OverviewViewMode = "3d" | "top"; -export interface OverviewSpatialMetadata { height_min_m: number | null; height_max_m: number | null; sample_points: number; } +export type OverviewRepresentation = "original" | "corrected"; +export interface OverviewComparison { + generation: string; map_generation: string; sample_points: number; source_points: number; + original_path_m: number; corrected_path_m: number; height_min_m: number; height_max_m: number; +} +export interface OverviewSpatialMetadata { + default_representation?: OverviewRepresentation; + height_min_m: number | null; height_max_m: number | null; sample_points: number; + comparison?: OverviewComparison; +} function endpoint(source: string) { const url = new URL(source, window.location.origin); @@ -12,12 +21,15 @@ export async function fetchOverviewSpatial(source: string, signal: AbortSignal): if (!response.ok) throw new Error("Параметры среза недоступны."); return response.json(); } -export async function updateOverviewSpatial(source: string, ceiling: number | null, mode: OverviewViewMode | null, aspect: number, signal: AbortSignal) { +export async function updateOverviewSpatial(source: string, ceiling: number | null, mode: OverviewViewMode | null, aspect: number, signal: AbortSignal, + comparisonGeneration: string | null = null, representation: OverviewRepresentation = "original") { + if (representation === "corrected" && !comparisonGeneration) throw new Error("Исправленная версия недоступна."); const url = endpoint(source); const generation = url.searchParams.get("generation"); url.search = ""; const response = await fetch(url, { method: "POST", signal, headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ generation, ceiling_m: ceiling, mode, aspect }) }); + body: JSON.stringify({ generation, ceiling_m: ceiling, mode, aspect, + comparison_generation: comparisonGeneration, representation }) }); if (!response.ok) throw new Error("Не удалось обновить вид облака."); const bytes = new Uint8Array(await response.arrayBuffer()); if (bytes.byteLength > 32 * 1024 * 1024) throw new Error("Обзор превышает допустимый размер."); diff --git a/apps/control-station/src/core/observation/useSessionDisplayProfile.ts b/apps/control-station/src/core/observation/useSessionDisplayProfile.ts new file mode 100644 index 0000000..a60c60d --- /dev/null +++ b/apps/control-station/src/core/observation/useSessionDisplayProfile.ts @@ -0,0 +1,38 @@ +import {useCallback, useEffect, useRef, useState} from 'react'; +import {defaultSceneSettings, type SceneSettings} from '../../sceneSettings'; +import {loadSessionDisplay, saveSessionDisplay} from './sessionDisplayProfile'; + +/** Session-bound read/write fencing. No viewer, playback or device ownership. */ +export function useSessionDisplayProfile(sessionId: string | null, apply: (settings: SceneSettings) => void) { + const [error, setError] = useState(null); + const applyRef = useRef(apply); applyRef.current = apply; + const current = useRef(sessionId); current.current = sessionId; + const writeTail = useRef(Promise.resolve()); + const revision = useRef(0); + useEffect(() => { + setError(null); + if (!sessionId) return; + const abort = new AbortController(); + const readRevision = revision.current; + applyRef.current({...defaultSceneSettings}); + void writeTail.current.then(() => loadSessionDisplay(sessionId, abort.signal)).then(settings => { + if (abort.signal.aborted || current.current !== sessionId) return; + if (revision.current === readRevision) applyRef.current(settings); + }).catch(reason => { + if (!abort.signal.aborted && current.current === sessionId) setError(String(reason.message ?? reason)); + }); + return () => abort.abort(); + }, [sessionId]); + const edited = useCallback(() => { revision.current += 1; }, []); + const save = useCallback((settings: SceneSettings) => { + const id = current.current; + if (!id) return; + const snapshot = {...settings}; + writeTail.current = writeTail.current.then(() => saveSessionDisplay(id, snapshot)).then(() => { + if (current.current === id) setError(null); + }).catch(reason => { + if (current.current === id) setError(String(reason.message ?? reason)); + }); + }, []); + return {save, edited, error, dismissError: () => setError(null)}; +} diff --git a/apps/control-station/src/workspaces/missions/MissionPlannerWorkspace.tsx b/apps/control-station/src/workspaces/missions/MissionPlannerWorkspace.tsx index 921729a..2a774ef 100644 --- a/apps/control-station/src/workspaces/missions/MissionPlannerWorkspace.tsx +++ b/apps/control-station/src/workspaces/missions/MissionPlannerWorkspace.tsx @@ -3,7 +3,6 @@ import { createPortal } from 'react-dom'; import { Button, ConfirmationModal, GlassSurface, Icon, IconButton, LoadingRegion, SegmentedControl, StatusBadge, WorkspaceWindow, type WorkspaceWindowRect } from '@nodedc/ui-react'; import { usePlanningTest } from '../../core/missions/PlanningTestContext'; import { useMissionPlanner } from '../../core/missions/useMissionPlanner'; -import { useRegistrationTest } from '../../core/missions/useRegistrationTest'; import { usePlanningProjects } from '../../core/missions/usePlanningProjects'; import { planningProjectPending, planningProjectStatus } from '../../core/missions/planningProjects'; import { MissionZonePreview } from '../../components/missions/MissionZonePreview'; @@ -17,9 +16,7 @@ import '../../styles/mission-planner.css'; export function MissionPlannerWorkspace({openView,headerToolsHost}:{openView:(id:string,profile?:'planning')=>void;headerToolsHost?:HTMLElement|null}) { const live=usePlanningTest(), p=useMissionPlanner(), projects=usePlanningProjects(); - const t=useRegistrationTest(p.saved); const [creating,setCreating]=useState(false), [settingsOpen,setSettingsOpen]=useState(false); - const [mode,setMode]=useState<'scanner'|'recording'>('scanner'); const [view,setView]=useState<'cloud'|'route'>('cloud'); const [starting,setStarting]=useState(false); const [pending,setPending]=useState<(()=>void)|null>(null); @@ -39,7 +36,7 @@ export function MissionPlannerWorkspace({openView,headerToolsHost}:{openView:(id }; const choose=(key:string)=>replace(()=>{setCreating(false);setSettingsOpen(false);projects.select(key);}); const newProject=()=>replace(()=>{ - projects.select('');p.newDraft();t.setSessionId('');setMode('scanner');setView('cloud'); + projects.select('');p.newDraft();setView('cloud'); setCreating(true);setSettingsOpen(true);setMaximized(false);loadedDraft.current=''; }); const start=async()=>{ @@ -47,13 +44,8 @@ export function MissionPlannerWorkspace({openView,headerToolsHost}:{openView:(id try { const draft=p.saved&&!p.dirty?p.saved:await p.save(); if(!draft)return; - if(mode==='scanner') { - const next=await live.begin(draft); - if(next){projects.select('live:'+next.id);openView('local-device','planning');} - } else { - const result=await t.run(draft); - if(result){setCreating(false);projects.select('recorded:'+result.id);setSettingsOpen(true);} - } + const next=await live.begin(draft); + if(next){projects.select('live:'+next.id);openView('local-device','planning');} projects.refresh(); } finally {setStarting(false);} }; @@ -81,13 +73,13 @@ export function MissionPlannerWorkspace({openView,headerToolsHost}:{openView:(id {!p.sessionId?
Выберите сохранённую запись эталона в настройках.
:p.sourceError||p.sourceChanged?
{p.sourceError||'Исходная запись изменилась.'}
:!p.source? - :view==='cloud'?:} + :view==='cloud'?:} :projects.error?
{projects.error}
:project?<>
{project.name}{planningProjectStatus(project)}
{project.scene_url? :planningProjectPending(project)? - :
В этом проекте нет сохранённого совмещения.Исходная запись остаётся в разделе «Данные».
} + :
В этом проекте нет сохранённого совмещения.Исходная запись прохода сохранена отдельно от результата.
} {project.scene_url&&{project.result?.correspondence_colors?'Серый — эталон · цветной — повторный проход · зелёный — точки в пределах 0,5 м':'Серый — эталон · зелёный — повторный проход (ранний формат записи)'}} :projects.loading||projects.key? :
Выберите совмещённый маршрут или создайте проект кнопкой «+».
} @@ -96,7 +88,7 @@ export function MissionPlannerWorkspace({openView,headerToolsHost}:{openView:(id title="Настройки" minWidth={320} minHeight={260} active zIndex={100} onClose={()=>setSettingsOpen(false)} closeLabel="Закрыть настройки" moveLabel="Переместить настройки" resizeLabel="Изменить размер настроек" maximizeLabel="Развернуть настройки" restoreLabel="Восстановить настройки" className="planning-project__inspector"> - {editing?void start()} starting={starting}/> + {editing?void start()} starting={starting}/> :project?:

Выберите проект или создайте новый кнопкой «+».

} {project?.kind==='live'&&planningProjectPending(project)&&} {live.error&&editing&&

{live.error}

} diff --git a/apps/control-station/src/workspaces/recordings/SessionOverviewWorkspace.tsx b/apps/control-station/src/workspaces/recordings/SessionOverviewWorkspace.tsx index b2f338d..a90ce37 100644 --- a/apps/control-station/src/workspaces/recordings/SessionOverviewWorkspace.tsx +++ b/apps/control-station/src/workspaces/recordings/SessionOverviewWorkspace.tsx @@ -46,11 +46,11 @@ export function SessionOverviewWorkspace({ sessionId }: { sessionId: string }) { separatorLabel="Высота графика интервалов" className="session-overview__split" primary={ - {m?.spatial_available && data?.scene_url ? : <>

Облако и траектория

Пространственный обзор для этой записи недоступен.
} + {m?.spatial_available && data?.scene_url ? : <>

Облако и траектория

Пространственный обзор для этой записи недоступен.
} {!m?.spatial_available && Доступны сведения из каталога записи.} } secondary={ -

Сведения о записи

+

Сведения об исходной записи

{facts.map(([name, value]) =>
{name}
{value}
)}

Наблюдения точек включают повторные измерения. Длина пути и положения получены из записи и не являются независимой проверкой точности.

diff --git a/apps/control-station/src/workspaces/spatial/SpatialWorkspace.tsx b/apps/control-station/src/workspaces/spatial/SpatialWorkspace.tsx index e0fc5a3..8b87bcc 100644 --- a/apps/control-station/src/workspaces/spatial/SpatialWorkspace.tsx +++ b/apps/control-station/src/workspaces/spatial/SpatialWorkspace.tsx @@ -554,7 +554,7 @@ export function SpatialWorkspace({ ) : null} } - navigationReady={visualProfile ? false : presentedViewerStatus==='ready'} timeline={visualProfile ? null : <> {!pointCloudFocused && !floatingSourceMaximized && recordedPlaybackReady ? ( + timeline={visualProfile ? null : <> {!pointCloudFocused && !floatingSourceMaximized && recordedPlaybackReady ? ( { + const decoded = decodeObservationSessionReplay(replay({ map_generation: "b".repeat(64) })); + assert.equal(decoded.mapGeneration, "b".repeat(64)); + assert.equal(decodeObservationSessionReplay(replay()).mapGeneration, undefined); + for (const value of [null, "latest", "../private", 123]) { + assert.throws(() => decodeObservationSessionReplay(replay({ map_generation: value })), /версию/); + } +}); + const preparationEtag = '"prepare-20260717T131400Z"'; test("session catalog decodes canonical snake_case into a path-free camelCase model", () => { @@ -412,7 +421,7 @@ test("data recordings keep the compact session dropdown and laboratory results s assert.doesNotMatch(laboratorySource, /fetchAdvancedLaboratoryResults/); }); -test("recording preparation statuses share the viewer's left alignment", async () => { +test("recording preparation statuses occupy the viewer's upper-right corner", async () => { const spatialStyles = await readFile( new URL("../../../packages/spatial-ui/src/spatial.css", import.meta.url), "utf8", @@ -422,9 +431,10 @@ test("recording preparation statuses share the viewer's left alignment", async ( spatialStyles.indexOf(".scene-operation-status {"), ); - assert.match(statusStack, /left:\s*0\.85rem/); - assert.match(statusStack, /justify-items:\s*start/); - assert.doesNotMatch(statusStack, /right:/); + assert.match(statusStack, /right:\s*0\.85rem/); + assert.match(statusStack, /top:\s*0\.85rem/); + assert.match(statusStack, /justify-items:\s*end/); + assert.doesNotMatch(statusStack, /left:|bottom:/); }); test("source and laboratory catalogs are requested as disjoint backend projections", async () => { diff --git a/apps/control-station/test/observationSources.test.mjs b/apps/control-station/test/observationSources.test.mjs index d1cc100..4b521ed 100644 --- a/apps/control-station/test/observationSources.test.mjs +++ b/apps/control-station/test/observationSources.test.mjs @@ -150,14 +150,15 @@ test("follow toggles retain the current recorded camera journal", () => { assert.notEqual(afterReset, before); }); -test("observation camera windows tile from the bottom-right above the live timeline", () => { +test("observation camera windows tile from the bottom-left above the live timeline", () => { const bounds = { width: 1280, height: 720 }; const left = initialObservationWindowRect(0, 2, bounds); const right = initialObservationWindowRect(1, 2, bounds); assert.equal(left.y, right.y); assert.ok(left.x + left.width < right.x); - assert.equal(right.x + right.width, bounds.width - 18); + assert.equal(left.x, 18); + assert.ok(right.x + right.width <= bounds.width - 18); assert.ok(right.y + right.height <= bounds.height - 64); }); @@ -323,10 +324,14 @@ test("recorded observation timeline rejects empty and non-finite ranges", () => test("accumulation control normalizes UI values and distinguishes a single frame", () => { assert.equal(normalizeAccumulationSeconds(-3), 0); assert.equal(normalizeAccumulationSeconds(12.6), 13); - assert.equal(normalizeAccumulationSeconds(999), 120); + assert.equal(normalizeAccumulationSeconds(999), 999); + assert.equal(normalizeAccumulationSeconds(1800), 1800); + assert.equal(normalizeAccumulationSeconds(9999), 9999); assert.equal(normalizeAccumulationSeconds(Number.NaN), 0); assert.equal(formatAccumulationDuration(0), "Кадр"); assert.equal(formatAccumulationDuration(12), "12 с"); + assert.equal(formatAccumulationDuration(1800), "30 мин"); + assert.equal(formatAccumulationDuration(125), "2 мин 5 с"); }); test("spatial timeline renders synchronized accumulation and playback controls", () => { @@ -606,6 +611,32 @@ test("recorded blueprint fetch is bounded, strict and sends only display setting ); }); +test("recorded tracking quantizes the native fractional-nanosecond cursor without accepting invalid times", async () => { + const bodies = []; + const request = currentTimeNs => fetchRecordedBlueprintRrd( + "/api/v1/observation-sessions/session-1/blueprint.rrd", + {accumulationSeconds: 10, showGrid: true, showPoints: true, + showTrajectory: true, pointSize: 0.5, colorMode: "intensity", + palette: "turbo", customColor: "#ffffff"}, + {applicationId: "nodedc_mission_core_recorded", recordingId: "recording-001"}, + {origin: "http://127.0.0.1:8000", blueprintSessionId: "a".repeat(32), + cameraEye: {position: [3, 4, 5], lookTarget: [1, 2, 0], eyeUp: [0, 0, 1]}, + eyeRelativeToTracking: true, currentTimeNs, + fetcher: async (_input, init) => { + bodies.push(JSON.parse(init.body)); + return new Response(new Uint8Array([0x52, 0x52, 0x46, 0x32]), { + headers: {"Content-Type": "application/vnd.rerun.rrd"}, + }); + }}, + ); + await request(536_460_021_972.65625); + assert.equal(bodies[0].current_time_ns, 536_460_021_973); + for (const invalid of [-0.1, NaN, Infinity, Number.MAX_SAFE_INTEGER + 1]) { + await assert.rejects(request(invalid), /Unsafe recorded blueprint request/); + } + assert.equal(bodies.length, 1); +}); + test("recorded point colors use one strict same-origin component overlay", async () => { const endpoint = resolveRecordedPointColorsUrl( "/api/v1/observation-sessions/session-1/recording.rrd", diff --git a/apps/control-station/test/planningWholeRecording.test.mjs b/apps/control-station/test/planningWholeRecording.test.mjs new file mode 100644 index 0000000..ce2fa31 --- /dev/null +++ b/apps/control-station/test/planningWholeRecording.test.mjs @@ -0,0 +1,76 @@ +import assert from 'node:assert/strict'; +import {before,after,test} from 'node:test'; +import React from 'react'; +import {renderToStaticMarkup} from 'react-dom/server'; +import {createServer} from 'vite'; +import {readFile} from 'node:fs/promises'; + +let server,usePlanner,Settings; +before(async()=>{ + server=await createServer({appType:'custom',logLevel:'silent',server:{middlewareMode:true}}); + ({useMissionPlanner:usePlanner}=await server.ssrLoadModule('/src/core/missions/useMissionPlanner.ts')); + ({PlanningProjectSettings:Settings}=await server.ssrLoadModule('/src/components/missions/PlanningProjectSettings.tsx')); +}); +after(async()=>{await server?.close();}); + +// Bounded hook dispatcher: execute the real state/effect transitions, without +// mounting another browser or replacing the production hook with a fake. +function harness(){ + const slots=[];let index=0,pending=[]; + const hooks={ + useState(initial){const i=index++;if(!slots[i])slots[i]={value:initial};return [slots[i].value,v=>{slots[i].value=typeof v==='function'?v(slots[i].value):v;}];}, + useEffect(fn,deps){const i=index++,old=slots[i];if(!old||deps.some((d,n)=>!Object.is(d,old.deps[n])))pending.push(()=>{old?.cleanup?.();slots[i]={deps,cleanup:fn()};});}, + useMemo(fn){return fn();},useCallback(fn){return fn;}, + }; + const render=()=>{index=0;const internal=React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,old=internal.H;internal.H=hooks;try{return usePlanner();}finally{internal.H=old;}}; + return {render,async settle(){let state;for(let n=0;n<5;n++){state=render();const effects=pending;pending=[];effects.forEach(fn=>fn());await new Promise(resolve=>setImmediate(resolve));}return render();},close(){slots.forEach(s=>s.cleanup?.());}}; +} +const source={schema_version:'missioncore.planning-source/v1',session_id:'ring',generation:'a'.repeat(64),label:'Ring',units:'m',path_m:580, + poses:Array.from({length:60},(_,index)=>({index,position:[index*10,0,0],distance_m:index*10}))}; + +test('same recording reselection cannot collapse the reference or disable a named project',async()=>{ + const oldFetch=globalThis.fetch,requests=[];const h=harness(); + globalThis.fetch=async(url,options={})=>{ + requests.push({url,body:options.body&&JSON.parse(options.body)}); + if(options.method==='POST')return {ok:true,json:async()=>({id:'draft',revision:1,name:'Seam',zone:{session_id:'ring',generation:source.generation},route:{start_index:0,end_index:59,direction:'forward'}})}; + return {ok:true,json:async()=>url.includes('/sources/')?source:{items:[],next_cursor:null}}; + }; + try{ + let p=await h.settle();p.chooseSource('ring');p.setName('Seam');p=await h.settle(); + assert.equal(p.poses.length,60);assert.equal(p.ready,true); + for(let i=0;i<4;i++){p.chooseSource('ring');p=await h.settle();assert.equal(p.poses.length,60);assert.equal(p.ready,true);} + await p.save();const body=requests.find(r=>r.body)?.body; + assert.equal(body.whole_recording,true);assert.equal(body.start_index,undefined);assert.equal(body.end_index,undefined); + assert.ok(requests.some(r=>r.url.includes('scope=standalone'))); + p=h.render();p.newDraft();p=await h.settle();p.chooseSource('ring');p.setName('Next');p=await h.settle(); + assert.equal(p.poses.length,60);assert.equal(p.ready,true); + assert.equal(p.setStart,undefined);assert.equal(p.setEnd,undefined); + }finally{h.close();globalThis.fetch=oldFetch;} +}); + +test('product removes reference crop controls and Data requests independent captures',async()=>{ + const settings=await readFile(new URL('../src/components/missions/PlanningProjectSettings.tsx',import.meta.url),'utf8'); + assert.doesNotMatch(settings,/Участок эталона|Начало участка|Конец участка|30 м от начала участка|p\.set(Start|End)/); + assert.match(settings,/Вся запись/); + const selector=await readFile(new URL('../src/components/ObservationSessionSelect.tsx',import.meta.url),'utf8'); + assert.match(selector,/scope: "standalone"/); +}); + +test('planner offers one direct live start without a repeated-pass mode or catalogue',async()=>{ + const p={name:'Ring',poses:source.poses,source,sessionId:'ring',options:[{value:'ring',label:'Ring'}],direction:'forward',ready:true}; + const render=(overrides={},starting=false)=>renderToStaticMarkup(React.createElement(Settings,{p:{...p,...overrides},starting,onStart:()=>{}})); + const ready=render(); + assert.doesNotMatch(ready,/Повторный проход|Из записи|Источник повторного прохода|Повторная запись|После запуска/); + const startButton=html=>[...html.matchAll(/]*>[\s\S]*?<\/button>/g)].map(m=>m[0]).find(button=>button.includes('Начать новый проход')); + assert.ok(startButton(ready)); + assert.doesNotMatch(startButton(ready),/disabled=/); + for(const state of [{ready:false},{busy:true},{poses:source.poses.slice(0,1)}])assert.match(startButton(render(state)),/disabled=/); + assert.match(startButton(render({},true)),/disabled=/); + let started=0; + const tree=Settings({p,starting:false,onStart:()=>{started++;}}); + tree.props.children[1].props.children[1].props.onClick(); + assert.equal(started,1); + const workspace=await readFile(new URL('../src/workspaces/missions/MissionPlannerWorkspace.tsx',import.meta.url),'utf8'); + assert.doesNotMatch(workspace,/useRegistrationTest|setMode|t\.run\(/); + assert.match(workspace,/live\.begin\(draft\)/); +}); diff --git a/apps/control-station/test/recordedRerunCameraJournal.test.mjs b/apps/control-station/test/recordedRerunCameraJournal.test.mjs index d36b03b..b89e4fe 100644 --- a/apps/control-station/test/recordedRerunCameraJournal.test.mjs +++ b/apps/control-station/test/recordedRerunCameraJournal.test.mjs @@ -1,101 +1,42 @@ import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; import { after, before, test } from "node:test"; import { createServer } from "vite"; -let server, createRecordedRerunCameraJournal, initialEye; +let server, readNativeRerunCameraEye; before(async () => { server = await createServer({ appType: "custom", logLevel: "silent", server: { middlewareMode: true } }); - const module = await server.ssrLoadModule( - "/src/components/rerun/recordedRerunCameraJournal.ts"); - createRecordedRerunCameraJournal = module.createRecordedRerunCameraJournal; - initialEye = module.RECORDED_RERUN_ORBITAL_EYE; + ({ readNativeRerunCameraEye } = await server.ssrLoadModule( + "/src/components/rerun/recordedRerunCameraJournal.ts")); }); after(async () => { await server?.close(); }); -class FakeEvent { - constructor(type, init = {}) { this.type = type; Object.assign(this, init); } -} - -class FakeTarget { - listeners = new Map(); - emitted = []; - addEventListener(type, listener) { - const listeners = this.listeners.get(type) ?? []; - listeners.push(listener); this.listeners.set(type, listeners); - } - removeEventListener(type, listener) { - this.listeners.set(type, (this.listeners.get(type) ?? []).filter(item => item !== listener)); - } - dispatchEvent(event) { - this.emitted.push(event); - for (const listener of this.listeners.get(event.type) ?? []) listener(event); - return true; +test("camera snapshot copies exact native pose, without approximating input", () => { + const native = { position: [287, -74, 8], lookTarget: [286, -72, 0], eyeUp: [0, 0, 1] }; + const eye = readNativeRerunCameraEye(native); + assert.deepEqual(eye, native); + native.position[0] = 999; + assert.equal(eye.position[0], 287); + assert.equal(readNativeRerunCameraEye(null), null); +}); + +test("invalid native pose fails closed instead of substituting a guessed camera", () => { + for (const value of [{}, "pose", { position: [NaN, 0, 1], lookTarget: [0, 0, 0], eyeUp: [0, 0, 1] }]) { + assert.throws(() => readNativeRerunCameraEye(value), /Invalid native Rerun camera/); } -} - -const radius = (eye) => Math.hypot( - eye.position[0] - eye.lookTarget[0], - eye.position[1] - eye.lookTarget[1], - eye.position[2] - eye.lookTarget[2], -); - -test("recorded orbital eye tracks only 3D viewport navigation", () => { - const scope = new FakeTarget(); - const timers = []; - Object.assign(scope, { - WheelEvent: FakeEvent, - requestAnimationFrame(callback) { callback(); return 1; }, - setTimeout(callback) { timers.push(callback); return timers.length; }, - }); - const canvas = new FakeTarget(); - Object.assign(canvas, { - clientHeight: 200, - isConnected: true, - getBoundingClientRect: () => ({ - left: 100, right: 500, top: 50, bottom: 250, width: 400, height: 200, - }), - }); - const journal = createRecordedRerunCameraJournal(canvas, scope); - journal.configure(initialEye, 0.46); - const pointer = (type, x, y, buttons) => new FakeEvent(type, { - clientX: x, clientY: y, button: 0, buttons, pointerId: 7, pointerType: "mouse", - altKey: false, ctrlKey: false, metaKey: false, shiftKey: false, - }); - - canvas.dispatchEvent(pointer("pointerdown", 200, 100, 1)); - scope.dispatchEvent(pointer("pointermove", 240, 120, 1)); - scope.dispatchEvent(pointer("pointerup", 240, 120, 0)); - assert.deepEqual(journal.current(), initialEye); - - canvas.dispatchEvent(pointer("pointerdown", 380, 100, 1)); - scope.dispatchEvent(pointer("pointermove", 420, 120, 1)); - scope.dispatchEvent(pointer("pointerup", 420, 120, 0)); - const rotated = journal.current(); - assert.notDeepEqual(rotated.position, initialEye.position); - assert.ok(Math.abs(radius(rotated) - radius(initialEye)) < 1e-9); - - journal.setMaxOrbitalRadius(40); - scope.dispatchEvent(new FakeEvent("wheel", { - clientX: 380, clientY: 130, deltaX: 0, deltaY: 2_000, deltaMode: 0, - altKey: false, ctrlKey: false, metaKey: false, shiftKey: false, - })); - assert.ok(Math.abs(radius(journal.current()) - 40) < 1e-9); - - journal.configure(rotated, 0.46); - - scope.dispatchEvent(new FakeEvent("wheel", { - clientX: 0, clientY: 0, deltaX: 0, deltaY: -20, deltaMode: 0, - altKey: false, ctrlKey: false, metaKey: false, shiftKey: false, - })); - const zoomed = journal.current(); - assert.ok(Math.abs(radius(zoomed) - radius(rotated) * Math.exp(-20 / 200)) < 1e-9); - - const snapshot = journal.current(); - journal.setSpatialViewportStart(0.8); - scope.dispatchEvent(new FakeEvent("wheel", { - clientX: 380, clientY: 130, deltaX: 0, deltaY: -20, deltaMode: 0, - })); - assert.deepEqual(journal.current(), snapshot); - journal.dispose(); +}); + +test("iframe owner reads the native camera and installs no shadow input listeners", () => { + const owner = readFileSync(new URL("../src/components/rerun/recordedRerunOwner.ts", import.meta.url), "utf8"); + const camera = readFileSync(new URL("../src/components/rerun/recordedRerunCameraJournal.ts", import.meta.url), "utf8"); + assert.match(owner, /get_camera_eye\?\.\(\)/); + assert.doesNotMatch(owner + camera, /createRecordedRerunCameraJournal|addEventListener|Math\.exp/); +}); + +test("display blueprint activation carries the current native eye, not the embedded preset", () => { + const viewport = readFileSync(new URL("../src/components/RerunViewport.tsx", import.meta.url), "utf8"); + assert.match(viewport, /!enablingFollow \? active\.getCameraEye\?\.\(\) \?\? undefined : undefined/); + assert.match(viewport, /requestBlueprint\(\s*firstEye,\s*firstEyeIsTrackingRelative,/); + assert.match(viewport, /currentTimeNs: eyeRelativeToTracking \? currentTimeNs : undefined/); }); diff --git a/apps/control-station/test/recordedViewerResourceLifecycle.test.mjs b/apps/control-station/test/recordedViewerResourceLifecycle.test.mjs index e938af5..c68a14e 100644 --- a/apps/control-station/test/recordedViewerResourceLifecycle.test.mjs +++ b/apps/control-station/test/recordedViewerResourceLifecycle.test.mjs @@ -59,6 +59,17 @@ function bridge(native, mount = {}) { return parent; } +test("camera snapshot crosses the disposable realm as copied primitive data", () => { + const eye = { position: [300, 7, 8], lookTarget: [298, 9, 0], eyeUp: [0, 0, 1] }; + const { facade, dispose } = bridge({ stop() {}, get_camera_eye: () => eye }); + const received = facade.get_camera_eye(); + assert.deepEqual(received, eye); + assert.notEqual(received, eye); + assert.notEqual(received.position, eye.position); + dispose(); + assert.throws(() => facade.get_camera_eye(), /disposed/); +}); + test("recorded realm terminates on close even if upstream stop throws", async (t) => { const f = fixture(t, { stopFails: true }); const scope = createIsolatedRerunHost(f.host); diff --git a/apps/control-station/test/rerunViewportAtomicAdmission.test.mjs b/apps/control-station/test/rerunViewportAtomicAdmission.test.mjs index e84225a..7f065cb 100644 --- a/apps/control-station/test/rerunViewportAtomicAdmission.test.mjs +++ b/apps/control-station/test/rerunViewportAtomicAdmission.test.mjs @@ -251,6 +251,10 @@ test("recorded RRD bytes are never split across LogChannel.send_rrd calls", asyn assert.match(source, /recordedPerceptionLayers\.costmap,/); assert.match(source, /recordedPerceptionLayers\.costmap !== undefined && status !== "ready"/); assert.match(source, /perceptionLayers\.costmap === undefined \? \{\} : \{\s*show_costmap: perceptionLayers\.costmap,\s*reactivate_updates: true/s); + // Ordinary archives need activation too, not only the LAB costmap path. + assert.match(source, /requestBlueprint\(\s*firstEye,\s*firstEyeIsTrackingRelative,[\s\S]*?\n\s*true,\s*\);/); + // Display changes must not trigger a full-file camera-bounds scan. + assert.match(source, /currentTimeNs: eyeRelativeToTracking \? currentTimeNs : undefined/); assert.match(source, /viewer\.start\(\s*rerunViewerInitialSource\(resolvedSource\)/s); assert.doesNotMatch(source, /rerunViewerOpenOptions/); assert.match( diff --git a/apps/control-station/test/rerunWebViewerVendor.test.mjs b/apps/control-station/test/rerunWebViewerVendor.test.mjs index a65c6c3..0bb8dff 100644 --- a/apps/control-station/test/rerunWebViewerVendor.test.mjs +++ b/apps/control-station/test/rerunWebViewerVendor.test.mjs @@ -1,4 +1,5 @@ import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import test from "node:test"; @@ -7,13 +8,41 @@ const root = resolve(import.meta.dirname, ".."); const packageRoot = resolve(root, "node_modules/@rerun-io/web-viewer"); const readJson = (path) => JSON.parse(readFileSync(path, "utf8")); -test("Mission Core uses the exact upstream Rerun 0.36.3 web package", () => { +test("Mission Core pins Rerun 0.36.3 with the bounded native navigation installer", () => { const application = readJson(resolve(root, "package.json")); const installed = readJson(resolve(packageRoot, "package.json")); assert.equal(application.dependencies["@rerun-io/web-viewer"], "0.36.3"); assert.equal(installed.version, "0.36.3"); - assert.equal(application.scripts.postinstall, undefined); + assert.equal(application.scripts.postinstall, "node scripts/install-rerun-navigation.mjs"); + assert.equal(application.scripts.prebuild, application.scripts.postinstall); +}); + +test("native navigation artifacts and source patch match their provenance", () => { + const vendorRoot = resolve(root, "vendor/rerun-web-viewer-0.36.3"); + const manifest = readJson(resolve(vendorRoot, "navigation-build.json")); + const sha = bytes => createHash("sha256").update(bytes).digest("hex"); + assert.equal(manifest.upstreamVersion, "0.36.3"); + assert.equal(manifest.upstreamCommit, "6ded109d33c549e98185f7c95fa8009d44e4adef"); + assert.equal(sha(readFileSync(resolve(vendorRoot, "NODEDC_NAVIGATION.patch"))), manifest.patchSha256); + for (const [name, identity] of Object.entries(manifest.files)) { + if (!identity.artifact) continue; + assert.equal(sha(readFileSync(resolve(vendorRoot, identity.artifact))), identity.sha256, name); + assert.equal(sha(readFileSync(resolve(packageRoot, name))), identity.sha256, name); + } +}); + +test("generated JS and native WASM share one ABI including the camera snapshot", () => { + const glue = readFileSync(resolve(packageRoot, "re_viewer.js"), "utf8"); + const wasm = new WebAssembly.Module(readFileSync(resolve(packageRoot, "re_viewer_bg.wasm"))); + const names = new Set(WebAssembly.Module.exports(wasm).map(item => item.name)); + assert.ok(names.has("webhandle_nodedc_camera_eye")); + for (const [, name] of glue.matchAll(/\bwasm\.([a-zA-Z_$][\w$]*)/g)) { + assert.ok(names.has(name), `Missing WASM export: ${name}`); + } + assert.match(readFileSync(resolve(packageRoot, "index.js"), "utf8"), /this\.#handle\.nodedc_camera_eye\(\)/); + assert.match(readFileSync(resolve(packageRoot, "index.d.ts"), "utf8"), /get_camera_eye\(\)/); + assert.match(readFileSync(resolve(packageRoot, "re_viewer.d.ts"), "utf8"), /nodedc_camera_eye\(\)/); }); test("the active application never imports or installs the archived vendor fork", () => { diff --git a/apps/control-station/test/sessionDisplayProfile.test.mjs b/apps/control-station/test/sessionDisplayProfile.test.mjs new file mode 100644 index 0000000..3b6acc0 --- /dev/null +++ b/apps/control-station/test/sessionDisplayProfile.test.mjs @@ -0,0 +1,134 @@ +import assert from 'node:assert/strict'; +import {before, after, test} from 'node:test'; +import {createServer} from 'vite'; +import {createElement} from 'react'; +import {renderToStaticMarkup} from 'react-dom/server'; +import {readFile} from 'node:fs/promises'; +import {runInNewContext} from 'node:vm'; +import ts from 'typescript'; + +let server, profile, points, defaults, Controls, Timeline; +before(async () => { + server = await createServer({appType: 'custom', logLevel: 'silent', server: {middlewareMode: true}}); + profile = await server.ssrLoadModule('/src/core/observation/sessionDisplayProfile.ts'); + points = await server.ssrLoadModule('/src/core/observation/recordedPointDisplay.ts'); + defaults = (await server.ssrLoadModule('/src/sceneSettings.ts')).defaultSceneSettings; + ({SceneDisplayControls: Controls} = await server.ssrLoadModule('../../packages/spatial-ui/src/SceneDisplayControls.tsx')); + ({ObservationTimeline: Timeline} = await server.ssrLoadModule('/src/components/ObservationTimeline.tsx')); +}); +after(async () => {await server?.close();}); + +const document = (settings, id='session-a') => ({schema_version: 'missioncore.session-display-profile/v1', + session_id: id, scene_settings: profile.encodeSessionDisplay(settings)}); + +test('session profile round trips all display settings and fractional decimation', () => { + const settings = {...defaults, accumulationMaxSeconds: 600, accumulationSeconds: 590, pointDecimationPercent: 49.5}; + assert.deepEqual(profile.decodeSessionDisplay(document(settings), 'session-a'), settings); + assert.throws(() => profile.decodeSessionDisplay(document(settings), 'session-b'), /другой записи/); + for (const percent of [-1, 100.1, NaN, Infinity]) { + assert.throws(() => profile.decodeSessionDisplay(document({...settings, pointDecimationPercent: percent}), 'session-a')); + } + for (const percent of [0, 50, 100]) assert.equal(profile.decodeSessionDisplay(document({...settings, + pointDecimationPercent: percent}), 'session-a').pointDecimationPercent, percent); + assert.equal(defaults.accumulationMaxSeconds, 180); +}); + +test('timeline uses per-session range instead of a fixed duration cap', () => { + const html = renderToStaticMarkup(createElement(Timeline, {accumulationSeconds: 47, + accumulationMaxSeconds: 600, onAccumulationChange: () => {}})); + assert.match(html, /max="600"/); +}); + +test('display exposes precise thinning only for saved playback', () => { + const props = {displayDraft: defaults, stageDisplayPatch: () => {}, commitDisplayPatch: () => {}, flushDisplaySettings: () => {}}; + assert.match(renderToStaticMarkup(createElement(Controls, {...props, replayPresented: true})), /Прореживание облака/); + assert.doesNotMatch(renderToStaticMarkup(createElement(Controls, props)), /Прореживание облака/); +}); + +test('open Display previews 100%, 0% and accumulation; close alone persists the latest draft', async () => { + // Execute the actual composition callbacks with deterministic timers. This + // catches the former open-inspector early return, not just helper behavior. + const source = await readFile(new URL('../src/App.tsx', import.meta.url), 'utf8'); + const ast = ts.createSourceFile('App.tsx', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); + const callbacks = new Map(); + const names = ['stageDisplayPatch', 'flushDisplaySettings', 'commitDisplaySettings']; + let close; + function visit(node) { + if (ts.isVariableDeclaration(node) && names.includes(node.name.getText(ast))) { + callbacks.set(node.name.getText(ast), node.initializer.getText(ast)); + } + if (ts.isBinaryExpression(node) && node.left.getText(ast) === 'closeDisplayRef.current') close = node.getText(ast); + ts.forEachChild(node, visit); + } + visit(ast); + assert.equal(callbacks.size, names.length); assert.ok(close); + const applied = [], saved = [], timers = new Map(); let timerId = 0; + const context = { + useCallback: fn => fn, displayWindowOpenRef: {current: true}, replayActiveRef: {current: true}, + displayDraftRef: {current: {...defaults, pointDecimationPercent: 86}}, setDisplayDraft() {}, + viewerSettingsCommitTimerRef: {current: null}, viewerSettingsQuietPeriodMs: 750, + sceneSettingsCommitterRef: {current: {enqueue: value => applied.push(value)}}, closeDisplayRef: {current: null}, + sessionDisplayProfile: {edited() {}, save: value => saved.push(value)}, + window: {setTimeout: fn => {timers.set(++timerId, fn); return timerId;}, clearTimeout: id => timers.delete(id)}, + }; + const code = [...callbacks].map(([name, init]) => `const ${name} = ${init};`).join('\n') + + `\n${close}; ({stageDisplayPatch, flushDisplaySettings, close: closeDisplayRef.current});`; + const actions = runInNewContext(ts.transpileModule(code, {compilerOptions: {target: ts.ScriptTarget.ES2022}}).outputText, context); + actions.stageDisplayPatch({pointDecimationPercent: 100}); + assert.equal(applied.at(-1).pointDecimationPercent, 100); + actions.stageDisplayPatch({pointDecimationPercent: 0}); + assert.equal(applied.at(-1).pointDecimationPercent, 0); + actions.stageDisplayPatch({accumulationSeconds: 180}); + actions.flushDisplaySettings(); + assert.equal(applied.at(-1).accumulationSeconds, 180); + actions.stageDisplayPatch({accumulationSeconds: 10}); + actions.flushDisplaySettings(); + assert.equal(applied.at(-1).accumulationSeconds, 10); + actions.stageDisplayPatch({pointDecimationPercent: 49.5}); + assert.equal(timers.size, 1); + const pending = [...timers.values()][0]; timers.clear(); pending(); + assert.equal(applied.at(-1).pointDecimationPercent, 49.5); + assert.equal(applied.at(-1).accumulationSeconds, 10); + assert.equal(saved.length, 0); + actions.close(); + assert.equal(saved.length, 1); + assert.equal(saved[0].pointDecimationPercent, 49.5); + assert.equal(saved[0].accumulationSeconds, 10); +}); + +test('display stream validates fragmented header and sends bounded chunks', async () => { + const previous = globalThis.window; globalThis.window = {location: {origin: 'http://localhost'}}; + try { + const wire = [78,80,68,49,8,0,0,0,82,82,70,50,1,2,3,4,0,0,0,0]; + const chunks = [new Uint8Array(wire.slice(0,1)), new Uint8Array(wire.slice(1,7)), + new Uint8Array(wire.slice(7,10)), new Uint8Array(wire.slice(10))]; + const sent = []; let request; + const fetcher = async (url, options) => {request = {url, ...options}; return new Response(new ReadableStream({ + start(controller) {chunks.forEach(chunk => controller.enqueue(chunk)); controller.close();}, + }), {headers: {'Content-Type': 'application/vnd.nodedc.point-display-stream'}});}; + const total = await points.streamRecordedPointDisplay('/api/v1/observation-sessions/session-a/blueprint.rrd', + {...defaults, pointDecimationPercent: 49.5}, {applicationId: 'app', recordingId: 'rec'}, 'a'.repeat(32), + new AbortController().signal, chunk => sent.push(...chunk), fetcher, 'a'.repeat(64)); + assert.equal(total, 20); assert.deepEqual(sent, [82,82,70,50,1,2,3,4]); + assert.equal(JSON.parse(request.body).point_decimation_percent, 49.5); + assert.equal(JSON.parse(request.body).source_generation, 'a'.repeat(64)); + assert.match(request.url, /point-display\.rrd$/); + } finally {globalThis.window = previous;} +}); + +test('invalid, cross-origin and canceled streams never become active', async () => { + const previous = globalThis.window; globalThis.window = {location: {origin: 'http://localhost'}}; + try { + const call = (url, signal, fetcher) => points.streamRecordedPointDisplay(url, {...defaults, pointDecimationPercent: 50}, + {applicationId: 'app', recordingId: 'rec'}, 'b'.repeat(32), signal, () => assert.fail('must not publish'), fetcher); + const url = '/api/v1/observation-sessions/session-a/blueprint.rrd'; + await assert.rejects(call('https://elsewhere.test'+url, new AbortController().signal, () => assert.fail('must not fetch'))); + await assert.rejects(call(url, new AbortController().signal, async () => new Response('bad!', { + headers: {'Content-Type': 'application/vnd.nodedc.point-display-stream'}})), /Некорректный поток/); + await assert.rejects(call(url, new AbortController().signal, async () => new Response('NPD1', { + headers: {'Content-Type': 'application/vnd.nodedc.point-display-stream'}})), /не завершён/); + const abort = new AbortController(); abort.abort(); + await assert.rejects(call(url, abort.signal, async () => new Response('RRF2', { + headers: {'Content-Type': 'application/vnd.nodedc.point-display-stream'}})), /Aborted/); + } finally {globalThis.window = previous;} +}); diff --git a/apps/control-station/test/sessionOverviewComparison.test.mjs b/apps/control-station/test/sessionOverviewComparison.test.mjs new file mode 100644 index 0000000..59f4407 --- /dev/null +++ b/apps/control-station/test/sessionOverviewComparison.test.mjs @@ -0,0 +1,55 @@ +import assert from 'node:assert/strict'; +import { before, after, test } from 'node:test'; +import { createServer } from 'vite'; +import { readFile } from 'node:fs/promises'; +let server, api; +before(async () => { + server = await createServer({ appType: 'custom', logLevel: 'silent', server: { middlewareMode: true } }); + api = await server.ssrLoadModule('/src/core/observation/sessionOverviewSpatial.ts'); +}); +after(async () => { await server?.close(); }); +test('comparison pins identity and changes geometry without requesting a camera reset', async () => { + const oldFetch = globalThis.fetch, oldWindow = globalThis.window; + const requests = []; + globalThis.window = { location: { origin: 'http://localhost:8000' } }; + globalThis.fetch = async (url, options) => { + requests.push({ url: String(url), body: JSON.parse(options.body) }); + return { ok: true, arrayBuffer: async () => new ArrayBuffer(8), headers: new Headers({ 'X-Overview-Visible-Points': '42' }) }; + }; + try { + const source = '/api/v1/observation-sessions/example/overview/scene.rrd?generation=' + 'a'.repeat(64); + const signal = new AbortController().signal; + for (const representation of ['original', 'corrected', 'original']) { + const result = await api.updateOverviewSpatial(source, 80, null, 1.5, signal, 'b'.repeat(64), representation); + assert.equal(result.eye, null); + assert.equal(result.visiblePoints, 42); + } + assert.deepEqual(requests.map(r => r.body.representation), ['original', 'corrected', 'original']); + for (const request of requests) { + assert.equal(request.body.mode, null); + assert.equal(request.body.comparison_generation, 'b'.repeat(64)); + assert.equal(request.body.generation, 'a'.repeat(64)); + assert.ok(request.url.endsWith('/overview/spatial')); + } + await assert.rejects(api.updateOverviewSpatial(source, null, null, 1, signal, null, 'corrected'), /недоступна/); + assert.equal(requests.length, 3); + } finally { globalThis.fetch = oldFetch; globalThis.window = oldWindow; } +}); +test('comparison control is opt-in while default geometry and pinned planner versions are shared', async () => { + const source = await readFile(new URL('../src/components/observation/SessionOverviewScene.tsx', import.meta.url), 'utf8'); + assert.match(source, /compareVersions=false/); + assert.match(source, /}, \[sourceUrl, retry\]\);/); + assert.match(source, /appliedMode.current === mode \? null : mode/); + assert.doesNotMatch(source, /key=\{representation\}|setRetry\([^\n]*representation/); + assert.match(source, /label="Версия облака"/); + assert.match(source, /setAppliedRepresentation\(comparison \? representation/); + assert.match(source, /setRepresentation\(data.default_representation/); + assert.match(source, /const comparison = metadata\?\.comparison/); + assert.match(source, /compareVersions && comparison/); + const planner = await readFile(new URL('../src/components/missions/MissionZonePreview.tsx', import.meta.url), 'utf8'); + assert.doesNotMatch(planner, /compareVersions/); + assert.match(planner, /reference_generation=\$\{encodeURIComponent\(generation\)\}/); + const logic = await readFile(new URL('../src/core/missions/useMissionPlanner.ts', import.meta.url), 'utf8'); + assert.match(logic, /setPinnedGeneration\(next.zone.generation\)/); + assert.match(logic, /setPinnedGeneration\(null\)/); +}); diff --git a/apps/control-station/test/spatialSceneLayout.test.mjs b/apps/control-station/test/spatialSceneLayout.test.mjs index e7c66ab..c7707a2 100644 --- a/apps/control-station/test/spatialSceneLayout.test.mjs +++ b/apps/control-station/test/spatialSceneLayout.test.mjs @@ -13,7 +13,8 @@ before(async () => { after(async () => { await server?.close(); }); const render = (focused = false) => renderToStaticMarkup(createElement(SpatialScene, { - viewportRef: { current: null }, primaryFocused: focused, toolbar: null, renderer: null, + viewportRef: { current: null }, primaryFocused: focused, toolbar: null, + renderer: createElement('div', { 'data-testid': 'renderer' }, 'RENDERER'), sourceControls: createElement('div', { className: focused ? 'scene-focus-exit' : 'scene-source-controls' }, 'SOURCE_CONTROLS'), status: { label: 'Накопление данных', tone: 'neutral', message: 'Сканер неподвижен.' }, metrics: createElement('div', null, 'METRICS'), @@ -37,6 +38,18 @@ test('focus exit remains viewport-owned outside the hidden information stack', ( assert.equal((markup.match(/SOURCE_CONTROLS/g) ?? []).length, 1); }); +test('normal and expanded scenes retain the renderer without mouse-navigation copy', async () => { + for (const focused of [false, true]) { + const markup = render(focused); + assert.match(markup, /data-testid="renderer">RENDERER/); + assert.doesNotMatch(markup, /scene-navigation-hint|Навигация по 3D-сцене|ЛКМ|ПКМ|Колесо/); + } + const source = await readFile(new URL('../../../packages/spatial-ui/src/SpatialScene.tsx', import.meta.url), 'utf8'); + const css = await readFile(new URL('../../../packages/spatial-ui/src/observation.css', import.meta.url), 'utf8'); + assert.doesNotMatch(source, /navigationReady|scene-navigation-hint/); + assert.doesNotMatch(css, /scene-navigation-hint/); +}); + test('scene layout uses flow, retains compact metrics and removes only the calibration perimeter', async () => { const css = await readFile(new URL('../../../packages/spatial-ui/src/spatial.css', import.meta.url), 'utf8'); const responsive = await readFile(new URL('../src/styles/responsive.css', import.meta.url), 'utf8'); @@ -48,3 +61,13 @@ test('scene layout uses flow, retains compact metrics and removes only the calib assert.doesNotMatch(responsive, /\.scene-metrics\s*\{\s*display: none/); assert.match(calibration, /\.xgrids-k1-spatial-controls \{[^}]*border: 0;/); }); + +test('preparation status shares the source controls top axis on the opposite side', async () => { + const css = await readFile(new URL('../../../packages/spatial-ui/src/spatial.css', import.meta.url), 'utf8'); + const stack = css.match(/\.scene-operation-status-stack \{[^}]*\}/)?.[0] ?? ''; + assert.match(stack, /top: 0\.85rem/); + assert.match(stack, /right: 0\.85rem/); + assert.match(stack, /min-height: 2\.75rem/); + assert.match(stack, /align-content: center/); + assert.doesNotMatch(stack, /left:|bottom:/); +}); diff --git a/apps/control-station/vendor/rerun-web-viewer-0.36.3/LICENSE-APACHE b/apps/control-station/vendor/rerun-web-viewer-0.36.3/LICENSE-APACHE new file mode 100644 index 0000000..11069ed --- /dev/null +++ b/apps/control-station/vendor/rerun-web-viewer-0.36.3/LICENSE-APACHE @@ -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 [yyyy] [name of copyright owner] + +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/apps/control-station/vendor/rerun-web-viewer-0.36.3/LICENSE-MIT b/apps/control-station/vendor/rerun-web-viewer-0.36.3/LICENSE-MIT new file mode 100644 index 0000000..79a5d00 --- /dev/null +++ b/apps/control-station/vendor/rerun-web-viewer-0.36.3/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2022 Rerun Technologies AB + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/apps/control-station/vendor/rerun-web-viewer-0.36.3/NODEDC_NAVIGATION.patch b/apps/control-station/vendor/rerun-web-viewer-0.36.3/NODEDC_NAVIGATION.patch new file mode 100644 index 0000000..413be2c --- /dev/null +++ b/apps/control-station/vendor/rerun-web-viewer-0.36.3/NODEDC_NAVIGATION.patch @@ -0,0 +1,361 @@ +--- a/crates/viewer/re_view_spatial/src/eye.rs ++++ b/crates/viewer/re_view_spatial/src/eye.rs +@@ -2,7 +2,7 @@ + use glam::{Mat4, Quat, Vec3, vec3}; + use macaw::IsoTransform; + use re_log_types::EntityPath; +-use re_sdk_types::blueprint::archetypes::EyeControls3D; ++use re_sdk_types::blueprint::archetypes::{EyeControls3D, LineGrid3D}; + use re_sdk_types::blueprint::components::{AngularSpeed, Eye3DKind}; + use re_sdk_types::components::{LinearSpeed, Position3D, Vector3D}; + use re_view::controls::{ +@@ -214,6 +214,8 @@ + pub last_look_target: Option, + pub last_orbit_radius: Option, + pub last_eye_up: Option, ++ /// NODE.DC: time of the last rendered eye, for the read-only WebViewer snapshot. ++ pub last_render_time: f64, + } + + #[derive(Clone, Debug, PartialEq, re_byte_size::SizeBytes)] +@@ -241,6 +243,7 @@ + fov_y: Option, + + did_interact: bool, ++ grid_plane: macaw::Plane3, + } + + impl EyeController { +@@ -250,16 +253,8 @@ + /// Avoids breaking the view by zooming in too far. + pub const MIN_ORBIT_DISTANCE: f32 = Eye::PERSPECTIVE_NEAR_PLANE * 2.0; + +- /// Cap on the orbital camera radius, as a multiple of the scene bounding box diagonal. +- /// +- /// Only applied when scroll-to-zoom wants to grow the radius further. Zoom-in, rotate, +- /// pan, WASD, and every other form of motion are left alone — this is intentionally a +- /// local restriction on orbital zoom-out only, not a general movement envelope. The 2D +- /// view has its own, separate zoom-out cap (see `ui_2d::MAX_ZOOM_OUT_FACTOR`). +- /// +- /// If the radius already exceeds this (e.g. right after loading) the current radius is +- /// used as the cap instead, so the camera isn't pulled back in. +- const MAX_ORBITAL_ZOOM_OUT_FACTOR: f32 = 5.0; ++ // NODE.DC: finite arithmetic guard only, independent of route/scene bounds. ++ const MAX_ORBITAL_RADIUS: f32 = 1.0e17; + + fn get_eye(&self) -> Eye { + Eye { +@@ -317,6 +312,10 @@ + .0, + ); + ++ let grid = ViewProperty::from_archetype::(ctx); ++ let grid_plane = grid.component_or_fallback::( ++ ctx, LineGrid3D::descriptor_plane().component, ++ )?; + Ok(Self { + pos, + look_target, +@@ -325,6 +324,7 @@ + eye_up, + did_interact: false, + fov_y, ++ grid_plane: grid_plane.into(), + }) + } + +@@ -482,12 +482,45 @@ + let up = rot * Vec3::Y; + let right = rot * -Vec3::X; // TODO(emilk): why do we need a negation here? O.o + ++ // Mission Core's map grid is XY. Screen-plane pan otherwise lifts the ++ // orbit target into the air, leaving scroll-to-target stranded there. ++ // First-person navigation intentionally retains upstream 3D movement. ++ let (right, up) = if self.kind == Eye3DKind::Orbital { ++ let normal = self.grid_plane.normal; ++ let project = |v: Vec3| v - normal * normal.dot(v); ++ let right = project(right).normalize_or(project(Vec3::X).normalize_or(Vec3::Y)); ++ let up = project(up).normalize_or(normal.cross(-right)); ++ (right, up) ++ } else { ++ (right, up) ++ }; + let translate = delta_in_view.x * right + delta_in_view.y * up; + + self.pos += translate; + self.look_target += translate; + } + ++ /// Translate the whole rig, not its orientation/radius, onto the grid. ++ /// Called only by deliberate navigation while not following an entity. ++ fn anchor_orbit_to_grid(&mut self) { ++ if self.kind == Eye3DKind::Orbital { ++ let correction = self.grid_plane.normal ++ * (self.grid_plane.d - self.grid_plane.normal.dot(self.look_target)); ++ self.pos += correction; ++ self.look_target += correction; ++ } ++ } ++ ++ fn zoom_orbit(&mut self, zoom_factor: f32) { ++ if !zoom_factor.is_finite() || zoom_factor <= 0.0 { ++ return; ++ } ++ let new_radius = (self.radius() / zoom_factor) ++ .clamp(Self::MIN_ORBIT_DISTANCE, Self::MAX_ORBITAL_RADIUS); ++ self.pos = self.look_target - self.fwd() * new_radius; ++ self.did_interact = true; ++ } ++ + fn handle_drag(&mut self, response: &egui::Response, drag_threshold: f32) { + if response.drag_delta().length() > drag_threshold { + let roll = response.dragged_by(ROLL_MOUSE) +@@ -513,7 +546,7 @@ + } + + /// Handle zoom/scroll input. +- fn handle_zoom(&mut self, egui_ctx: &egui::Context, scene_bounding_box: &macaw::BoundingBox) { ++ fn handle_zoom(&mut self, egui_ctx: &egui::Context, _scene_bounding_box: &macaw::BoundingBox) { + let zoom_factor = egui_ctx.input(|input| { + // egui's default horizontal_scroll_modifier is shift, which is also our speed-up modifier. + // This means that a user who wants to speed up scroll-to-zoom will generate a horizontal scroll delta. +@@ -528,22 +561,7 @@ + + match self.kind { + Eye3DKind::Orbital => { +- let radius = self.pos.distance(self.look_target); +- +- // Cap zoom-out against the scene bounding box. If we're already past the cap +- // (e.g. right after loading) use the current radius instead — no snap-back. +- let max_radius = max_orbital_radius(scene_bounding_box).max(radius); +- let new_radius = (radius / zoom_factor).clamp(Self::MIN_ORBIT_DISTANCE, max_radius); +- +- // The user may be scrolling to move the camera closer, but are not realizing +- // the radius is now tiny. +- // TODO(emilk): inform the users somehow that scrolling won't help, and that they should use WSAD instead. +- // It might be tempting to start moving the camera here on scroll, but that would is bad for other reasons. +- +- if f32::MIN_POSITIVE < new_radius { +- self.pos = self.look_target - self.fwd() * new_radius; +- self.did_interact = true; +- } ++ self.zoom_orbit(zoom_factor); + } + Eye3DKind::FirstPerson => { + // Move along the forward axis when zooming in first person mode. +@@ -707,25 +725,6 @@ + } + } + +-/// Cap on the orbital zoom-out radius, derived from the scene bounding box diagonal. +-/// +-/// Returns `1.0e17` (a large fallback that avoids infinities downstream) when no usable scene +-/// bounding box is available. +-fn max_orbital_radius(scene_bounding_box: &macaw::BoundingBox) -> f32 { +- // `1.0e17` fallback is chosen with generous margin of an observed crash due to infinity. +- let fallback = 1.0e17; +- +- if !scene_bounding_box.is_finite() || scene_bounding_box.is_nothing() { +- return fallback; +- } +- let scene_diagonal = scene_bounding_box.size().length(); +- if !scene_diagonal.is_finite() || scene_diagonal <= 0.0 { +- return fallback; +- } +- (scene_diagonal * EyeController::MAX_ORBITAL_ZOOM_OUT_FACTOR) +- .max(EyeController::MIN_ORBIT_DISTANCE) +-} +- + pub fn find_camera(cameras: &[PinholeWrapper], needle: &EntityPath) -> Option { + let mut found_camera = None; + +@@ -744,6 +743,103 @@ + + fn ease_out(t: f32) -> f32 { + 1. - (1. - t) * (1. - t) ++} ++ ++#[cfg(test)] ++mod nodedc_navigation_tests { ++ use super::*; ++ ++ fn controller() -> EyeController { ++ EyeController { ++ pos: vec3(305.0, -80.0, 22.0), ++ look_target: vec3(290.0, -60.0, 4.0), ++ kind: Eye3DKind::Orbital, ++ speed: 30.0, ++ eye_up: Vec3::Z, ++ fov_y: Some(Eye::DEFAULT_FOV_Y), ++ did_interact: false, ++ grid_plane: macaw::Plane3 { normal: Vec3::Z, d: 0.0 }, ++ } ++ } ++ ++ #[test] ++ fn pan_is_on_grid_and_moves_the_whole_rig() { ++ let mut c = controller(); ++ let offset = c.pos - c.look_target; ++ c.anchor_orbit_to_grid(); ++ for _ in 0..100 { ++ c.translate(egui::vec2(0.5, 0.7)); ++ } ++ assert_eq!(c.look_target.z, 0.0); ++ assert!((c.pos - c.look_target - offset).length() < 0.001); ++ } ++ ++ #[test] ++ fn rotation_keeps_selected_pivot_and_radius() { ++ let mut c = controller(); ++ c.anchor_orbit_to_grid(); ++ let target = c.look_target; ++ let radius = c.radius(); ++ for _ in 0..200 { ++ c.rotate(egui::vec2(2.0, 0.1)); ++ } ++ assert_eq!(c.look_target, target); ++ assert!((c.radius() - radius).abs() < 0.01); ++ } ++ ++ #[test] ++ fn zoom_passes_map_scale_without_moving_pivot() { ++ let mut c = controller(); ++ c.anchor_orbit_to_grid(); ++ let target = c.look_target; ++ c.zoom_orbit(0.001); ++ assert!(c.radius() > 20_000.0); ++ for _ in 0..12 { ++ c.zoom_orbit(4.0); ++ } ++ assert!(c.radius() < 0.03); ++ assert_eq!(c.look_target, target); ++ assert!(c.get_eye().world_from_rub_view.translation().is_finite()); ++ } ++ ++ #[test] ++ fn vertical_view_pan_stays_finite_on_grid() { ++ let mut c = controller(); ++ c.pos = vec3(0.0, 0.0, 30.0); ++ c.look_target = Vec3::ZERO; ++ c.eye_up = Vec3::Y; ++ c.translate(egui::vec2(1.0, 1.0)); ++ assert_eq!(c.look_target.z, 0.0); ++ assert!(c.pos.is_finite()); ++ assert!(c.look_target.length() > 1.0); ++ } ++ ++ #[test] ++ fn navigation_respects_the_actual_grid_plane() { ++ let mut c = controller(); ++ c.grid_plane = macaw::Plane3 { normal: Vec3::Y, d: 3.0 }; ++ c.anchor_orbit_to_grid(); ++ let target = c.look_target; ++ c.translate(egui::vec2(2.0, 4.0)); ++ c.rotate(egui::vec2(10.0, 3.0)); ++ assert_eq!(c.look_target.y, 3.0); ++ assert_ne!(c.look_target, target); ++ } ++ ++ #[test] ++ fn invalid_zoom_is_ignored_and_first_person_is_unchanged() { ++ let mut c = controller(); ++ let before = c.pos; ++ for factor in [0.0, -1.0, f32::NAN, f32::INFINITY] { ++ c.zoom_orbit(factor); ++ assert_eq!(c.pos, before); ++ } ++ c.kind = Eye3DKind::FirstPerson; ++ c.anchor_orbit_to_grid(); ++ assert_eq!(c.look_target.z, 4.0); ++ c.translate(egui::vec2(0.0, 1.0)); ++ assert_ne!(c.look_target.z, 4.0); ++ } + } + + impl EyeState { +@@ -804,9 +900,32 @@ + } + } + ++ // Anchor before the first paint, not on the first orbit drag. This ++ // avoids a one-off camera jump when the scene-bounds fallback is high. ++ if tracking_entity.is_none() && eye_controller.kind == Eye3DKind::Orbital ++ && (eye_controller.grid_plane.normal.dot(eye_controller.look_target) ++ - eye_controller.grid_plane.d).abs() > 1.0e-5 ++ { ++ eye_controller.anchor_orbit_to_grid(); ++ eye_controller.did_interact = true; ++ } ++ + // Handle spinning before inputs because some inputs depend on view direction. + self.handle_spinning(ctx, eye_property, &mut eye_controller)?; + ++ // NODE.DC: pin manual map navigation to the XY grid. Tracking and ++ // explicit camera presets retain their own target until manual input. ++ let manual_input = response.drag_delta().length_sq() > 0.0 ++ || (response.hovered() && response.ctx.input(|i| { ++ i.smooth_scroll_delta != egui::Vec2::ZERO || i.zoom_delta() != 1.0 ++ })); ++ if manual_input { ++ self.stop_interpolation(); ++ if tracking_entity.is_none() { ++ eye_controller.anchor_orbit_to_grid(); ++ } ++ } ++ + // We do input before tracking entity, because the input can cause the eye + // to stop tracking. + let gamepad_navigation_status = eye_controller.handle_input( +@@ -1241,6 +1360,7 @@ + }; + + self.last_eye = Some(eye); ++ self.last_render_time = ctx.egui_ctx().time(); + + Ok(eye) + } +--- a/crates/viewer/re_viewer_context/src/view/view_states.rs ++++ b/crates/viewer/re_viewer_context/src/view/view_states.rs +@@ -173,6 +173,13 @@ + } + + impl ViewStates { ++ /// NODE.DC: read-only per-recording state access for native camera snapshots. ++ pub fn states_for_store<'a>(&'a self, store_id: &'a StoreId) -> impl Iterator { ++ self.states.iter().filter_map(move |((id, _), state)| { ++ (id == store_id).then_some(state.as_ref()) ++ }) ++ } ++ + pub fn get(&self, store_id: &StoreId, view_id: ViewId) -> Option<&dyn ViewState> { + self.states + .get(&(store_id.clone(), view_id)) +--- a/crates/viewer/re_viewer/src/web.rs ++++ b/crates/viewer/re_viewer/src/web.rs +@@ -384,6 +384,26 @@ + let recording = hub.entity_db(recording_id)?; + + Some(recording.store_id().recording_id().to_string()) ++ } ++ ++ /// NODE.DC navigation/v1. Last rendered native eye, never a replay of DOM deltas. ++ /// Each Mission Core iframe admits one active 3D pane. Timestamp selection ++ /// excludes states retained for a previously selected/reset pane. ++ #[wasm_bindgen] ++ pub fn nodedc_camera_eye(&self) -> Option { ++ let app = self.runner.app_mut::()?; ++ let store_id = app.active_recording_id()?; ++ let eye_state = app.state.view_states.states_for_store(store_id) ++ .filter_map(|state| state.as_any().downcast_ref::()) ++ .map(|state| &state.state_3d.eye_state) ++ .filter(|state| state.last_eye.is_some() && state.last_look_target.is_some()) ++ .max_by(|a, b| a.last_render_time.total_cmp(&b.last_render_time))?; ++ let eye = eye_state.last_eye?; ++ Some(serde_json::json!({ ++ "position": eye.pos_in_world().to_array(), ++ "lookTarget": eye_state.last_look_target?.to_array(), ++ "eyeUp": eye_state.last_eye_up?.to_array(), ++ }).to_string()) + } + + //TODO(#10737): we should refer to logical recordings using store id (recording id is ambiguous) diff --git a/apps/control-station/vendor/rerun-web-viewer-0.36.3/README.md b/apps/control-station/vendor/rerun-web-viewer-0.36.3/README.md new file mode 100644 index 0000000..dbcd504 --- /dev/null +++ b/apps/control-station/vendor/rerun-web-viewer-0.36.3/README.md @@ -0,0 +1,44 @@ +# Rerun 0.36.3 · NODE.DC navigation/v1 + +This is the bounded native camera amendment in ADR 0052, not the archived +0.34.1 renderer fork. `navigation-build.json` binds the exact upstream commit, +portable source patch and paired generated runtime. Keep the upstream licenses. + +## Rebuild + +1. Download the source archive for the manifest's `upstreamCommit` from + `https://codeload.github.com/rerun-io/rerun/tar.gz/` and verify its hash. +2. Extract into a new temporary directory and run `git apply --check` followed + by `git apply` with `NODEDC_NAVIGATION.patch` at the extracted root. +3. Pack that root as `patched-source.tar.gz`. On macOS use + `COPYFILE_DISABLE=1 tar --no-mac-metadata --exclude='._*'`; AppleDouble files + are not shaders and must not enter the build. +4. On Worker006, create a temporary container from the pinned Rust image in + the manifest, with 4 CPUs, 16 GB memory, no GPU, no ports and no private data. + Use the `ndc-` name and NODE.DC ownership labels required by AGENTS.md. + Copy the archive to `/work/patched-source.tar.gz` and the repository's + `scripts/build-rerun-navigation.sh` to `/work/build-rerun-navigation.sh`. + Entrypoint: `bash /work/build-rerun-navigation.sh`. +5. Require the native navigation tests to pass. The upstream + `rerun_js/web-viewer/build-wasm.mjs --mode release` produces the matching + `re_viewer.js`, `re_viewer.d.ts` and `re_viewer_bg.wasm`. Copy all three, + never only WASM, to the artifact names in this directory's manifest. +6. Recheck source patch, build script and generated file hashes. Update the + manifest, run the installer twice (idempotency), vendor ABI checks, frontend + contracts/build, then real product-browser QA. Remove the temporary build + container after copying artifacts and evidence. + +The source archive and build cache are not runtime dependencies. No compiler, +Docker or hardware access is needed on a clean operator install: the existing +exact npm package plus `postinstall` installs the audited paired artifacts. +An unfamiliar package or artifact is rejected before any file is changed. + +## Upgrade / rollback + +Rebase against the next exact upstream source, not against minified JS. Retest +grid-plane pan, fixed-pivot orbit, close/far zoom, explicit reset/top/follow, +display updates, normal/expanded views and disposable-iframe cleanup. + +To roll back, disable `postinstall`/`prebuild`, restore the official exact npm +package and rebuild. Missing snapshots are represented as null, not simulated +camera state. Recordings, corrected maps and scanner sessions are unchanged. diff --git a/apps/control-station/vendor/rerun-web-viewer-0.36.3/navigation-build.json b/apps/control-station/vendor/rerun-web-viewer-0.36.3/navigation-build.json new file mode 100644 index 0000000..4b083f1 --- /dev/null +++ b/apps/control-station/vendor/rerun-web-viewer-0.36.3/navigation-build.json @@ -0,0 +1,35 @@ +{ + "schema": "nodedc.rerun-navigation/v1", + "upstreamVersion": "0.36.3", + "upstreamCommit": "6ded109d33c549e98185f7c95fa8009d44e4adef", + "upstreamArchiveSha256": "3f92aef3f33f63c79b275b5c66a65d73192e75967cbcd3d1f16c75010996f896", + "patchSha256": "a893a4b4e7523cca88284316c88b930c3b91ba973ed3f954c9ca632f71252e64", + "cargoLockSha256": "74cdf9c3c8c5dbe5a368f1a9a58b011e2a45720843b30e552bc7b1d8ac21d791", + "buildScriptSha256": "e3cbd5da889403eda3ffba7c020b6075bdfb025b823d1bf503b639f59688135a", + "buildDate": "2026-09-21", + "rustImage": "rust:1.95-bookworm@sha256:6258907abe69656e41cd992e0b705cdcfabcbbe3db374f92ed2d47121282d4a1", + "binaryenVersion": "117", + "binaryenArchiveSha256": "3dc677006555b355ea2da5e82602065a161d5e83eaefd3f759afa00b96e83212", + "wasmBindgenVersion": "0.2.126", + "nativeTestsPassed": 6, + "files": { + "index.js": { "upstreamSha256": "009aa5f66c8674ea268a91926c00e2f82cc7a7b7e5535056d93bd4656d6f6812" }, + "index.ts": { "upstreamSha256": "087b25170f720a28aa69c411903133286238900050ac775d2cc511b919846696" }, + "index.d.ts": { "upstreamSha256": "649637dad4e50ef4f2310fc1920b580d16832226a0dda5238355059bf167efba" }, + "re_viewer.js": { + "upstreamSha256": "d2a94836dd0e6de40538c4d657cfb423b1803e354e6ee0cd550735eb5aaab09b", + "artifact": "re_viewer.nodedc.js", + "sha256": "610d89abffcc5c329797e77794ae2ab5b426c80a5b23fd24064c7d47c6583d3a" + }, + "re_viewer.d.ts": { + "upstreamSha256": "9a77a4e1e1aa8311185a3f275bf326a8fff2844e468e561c59d5f0ee7aa6debe", + "artifact": "re_viewer.nodedc.d.ts", + "sha256": "cfbf010d66560af919aa68d255a6ddc7120829f8ee8f27a41f56d943cd54e598" + }, + "re_viewer_bg.wasm": { + "upstreamSha256": "02d6d9c3a569d3faceb01cf89104342d970fc85e7c31a08dedd357b6c7104d2f", + "artifact": "re_viewer_bg.nodedc.wasm", + "sha256": "2b661de545ac90bb950eda1a28fc5a7c3ce4ca7f5bb24894789acf7589495185" + } + } +} diff --git a/apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer.nodedc.d.ts b/apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer.nodedc.d.ts new file mode 100644 index 0000000..e89ac10 --- /dev/null +++ b/apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer.nodedc.d.ts @@ -0,0 +1,200 @@ + +declare namespace wasm_bindgen { + /* tslint:disable */ + /* eslint-disable */ + /** + * The `ReadableStreamType` enum. + * + * *This API requires the following crate features to be activated: `ReadableStreamType`* + */ + + export type ReadableStreamType = "bytes"; + + export class IntoUnderlyingByteSource { + private constructor(); + free(): void; + [Symbol.dispose](): void; + cancel(): void; + pull(controller: ReadableByteStreamController): Promise; + start(controller: ReadableByteStreamController): void; + readonly autoAllocateChunkSize: number; + readonly type: ReadableStreamType; + } + + export class IntoUnderlyingSink { + private constructor(); + free(): void; + [Symbol.dispose](): void; + abort(reason: any): Promise; + close(): Promise; + write(chunk: any): Promise; + } + + export class IntoUnderlyingSource { + private constructor(); + free(): void; + [Symbol.dispose](): void; + cancel(): void; + pull(controller: ReadableStreamDefaultController): Promise; + } + + export class WebHandle { + free(): void; + [Symbol.dispose](): void; + /** + * Add a new receiver streaming data from the given url. + * + * Websocket streams are always opened in `Following` mode. + * + * It is an error to open a channel twice with the same id. + */ + add_receiver(url: string): void; + /** + * Close an existing channel for streaming data. + * + * No-op if the channel is already closed. + */ + close_channel(id: string): void; + destroy(): void; + get_active_recording_id(): string | undefined; + get_active_timeline(recording_id: string): string | undefined; + get_playing(recording_id: string): boolean | undefined; + get_time_for_timeline(recording_id: string, timeline_name: string): number | undefined; + get_timeline_time_range(recording_id: string, timeline_name: string): any; + has_panicked(): boolean; + constructor(app_options: any); + /** + * NODE.DC navigation/v1. Last rendered native eye, never a replay of DOM deltas. + * Each Mission Core iframe admits one active 3D pane. Timestamp selection + * excludes states retained for a previously selected/reset pane. + */ + nodedc_camera_eye(): string | undefined; + /** + * Open a new channel for streaming data. + * + * It is an error to open a channel twice with the same id. + */ + open_channel(id: string, channel_name: string): void; + override_panel_state(panel: string, state?: string | null): void; + panic_callstack(): string | undefined; + panic_message(): string | undefined; + remove_receiver(url: string): void; + /** + * Add an rrd to the viewer directly from a byte array. + */ + send_rrd_to_channel(id: string, data: Uint8Array): void; + send_table_to_channel(id: string, data: Uint8Array): void; + set_active_recording_id(recording_id: string): void; + /** + * Set the active timeline. + * + * This does nothing if the timeline can't be found. + */ + set_active_timeline(recording_id: string, timeline_name: string): void; + set_credentials(access_token: string, email: string): void; + set_playing(recording_id: string, value: boolean): void; + set_time_for_timeline(recording_id: string, timeline_name: string, time: number): void; + start(canvas: any): Promise; + toggle_panel_overrides(value?: boolean | null): void; + } + +} +declare type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +declare interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly __wbg_webhandle_free: (a: number, b: number) => void; + readonly webhandle_add_receiver: (a: number, b: number, c: number) => void; + readonly webhandle_close_channel: (a: number, b: number, c: number) => void; + readonly webhandle_destroy: (a: number) => void; + readonly webhandle_get_active_recording_id: (a: number) => [number, number]; + readonly webhandle_get_active_timeline: (a: number, b: number, c: number) => [number, number]; + readonly webhandle_get_playing: (a: number, b: number, c: number) => number; + readonly webhandle_get_time_for_timeline: (a: number, b: number, c: number, d: number, e: number) => [number, number]; + readonly webhandle_get_timeline_time_range: (a: number, b: number, c: number, d: number, e: number) => any; + readonly webhandle_has_panicked: (a: number) => number; + readonly webhandle_new: (a: any) => [number, number, number]; + readonly webhandle_nodedc_camera_eye: (a: number) => [number, number]; + readonly webhandle_open_channel: (a: number, b: number, c: number, d: number, e: number) => void; + readonly webhandle_override_panel_state: (a: number, b: number, c: number, d: number, e: number) => [number, number]; + readonly webhandle_panic_callstack: (a: number) => [number, number]; + readonly webhandle_panic_message: (a: number) => [number, number]; + readonly webhandle_remove_receiver: (a: number, b: number, c: number) => void; + readonly webhandle_send_rrd_to_channel: (a: number, b: number, c: number, d: number, e: number) => void; + readonly webhandle_send_table_to_channel: (a: number, b: number, c: number, d: number, e: number) => void; + readonly webhandle_set_active_recording_id: (a: number, b: number, c: number) => void; + readonly webhandle_set_active_timeline: (a: number, b: number, c: number, d: number, e: number) => void; + readonly webhandle_set_credentials: (a: number, b: number, c: number, d: number, e: number) => void; + readonly webhandle_set_playing: (a: number, b: number, c: number, d: number) => void; + readonly webhandle_set_time_for_timeline: (a: number, b: number, c: number, d: number, e: number, f: number) => void; + readonly webhandle_start: (a: number, b: any) => any; + readonly webhandle_toggle_panel_overrides: (a: number, b: number) => void; + readonly rust_lz4_wasm_shim_calloc: (a: number, b: number) => number; + readonly rust_lz4_wasm_shim_free: (a: number) => void; + readonly rust_lz4_wasm_shim_malloc: (a: number) => number; + readonly rust_lz4_wasm_shim_memcmp: (a: number, b: number, c: number) => number; + readonly rust_lz4_wasm_shim_memcpy: (a: number, b: number, c: number) => number; + readonly rust_lz4_wasm_shim_memmove: (a: number, b: number, c: number) => number; + readonly rust_lz4_wasm_shim_memset: (a: number, b: number, c: number) => number; + readonly rust_zstd_wasm_shim_calloc: (a: number, b: number) => number; + readonly rust_zstd_wasm_shim_free: (a: number) => void; + readonly rust_zstd_wasm_shim_malloc: (a: number) => number; + readonly rust_zstd_wasm_shim_memcmp: (a: number, b: number, c: number) => number; + readonly rust_zstd_wasm_shim_memcpy: (a: number, b: number, c: number) => number; + readonly rust_zstd_wasm_shim_memmove: (a: number, b: number, c: number) => number; + readonly rust_zstd_wasm_shim_memset: (a: number, b: number, c: number) => number; + readonly rust_zstd_wasm_shim_qsort: (a: number, b: number, c: number, d: number) => void; + readonly __wbg_intounderlyingbytesource_free: (a: number, b: number) => void; + readonly __wbg_intounderlyingsink_free: (a: number, b: number) => void; + readonly __wbg_intounderlyingsource_free: (a: number, b: number) => void; + readonly intounderlyingbytesource_autoAllocateChunkSize: (a: number) => number; + readonly intounderlyingbytesource_cancel: (a: number) => void; + readonly intounderlyingbytesource_pull: (a: number, b: any) => any; + readonly intounderlyingbytesource_start: (a: number, b: any) => void; + readonly intounderlyingbytesource_type: (a: number) => number; + readonly intounderlyingsink_abort: (a: number, b: any) => any; + readonly intounderlyingsink_close: (a: number) => any; + readonly intounderlyingsink_write: (a: number, b: any) => any; + readonly intounderlyingsource_cancel: (a: number) => void; + readonly intounderlyingsource_pull: (a: number, b: any) => any; + readonly wasm_bindgen__convert__closures_____invoke__ha28703b0fc0ac5f5: (a: number, b: number, c: any) => [number, number]; + readonly wasm_bindgen__convert__closures_____invoke__h17feb392561402d4: (a: number, b: number, c: any) => [number, number]; + readonly wasm_bindgen__convert__closures_____invoke__hd1708d5debff0eb7: (a: number, b: number, c: any) => [number, number]; + readonly wasm_bindgen__convert__closures_____invoke__h17feb392561402d4_10: (a: number, b: number, c: any) => [number, number]; + readonly wasm_bindgen__convert__closures_____invoke__h17feb392561402d4_11: (a: number, b: number, c: any) => [number, number]; + readonly wasm_bindgen__convert__closures_____invoke__h68d110bc138a9729: (a: number, b: number, c: any) => [number, number]; + readonly wasm_bindgen__convert__closures_____invoke__h4a090e5af75dc439: (a: number, b: number, c: any, d: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h72e0675ea71ceaf9: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h76e373640bdbaace: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h912ec1a6c04fd19b: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h76e373640bdbaace_4: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__ha366fcce789d0db1: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h3d976baa4adbebda: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__h912ec1a6c04fd19b_9: (a: number, b: number, c: any) => void; + readonly wasm_bindgen__convert__closures_____invoke__hbf8a291ae3f8f46d: (a: number, b: number) => [number, number]; + readonly wasm_bindgen__convert__closures_____invoke__h0e2ab714131e0149: (a: number, b: number) => void; + readonly wasm_bindgen__convert__closures_____invoke__h2a38a854c15eed3d: (a: number, b: number) => void; + readonly wasm_bindgen__convert__closures_____invoke__h46084f6dced1097a: (a: number, b: number) => void; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; + readonly __externref_table_alloc: () => number; + readonly __wbindgen_externrefs: WebAssembly.Table; + readonly __wbindgen_exn_store: (a: number) => void; + readonly __wbindgen_free: (a: number, b: number, c: number) => void; + readonly __wbindgen_destroy_closure: (a: number, b: number) => void; + readonly __externref_table_dealloc: (a: number) => void; + readonly __wbindgen_start: () => void; +} + +/** + * If `module_or_path` is {RequestInfo} or {URL}, makes a request and + * for everything else, calls `WebAssembly.instantiate` directly. + * + * @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. + * + * @returns {Promise} + */ +declare function wasm_bindgen (module_or_path: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; + +export type WebHandle = wasm_bindgen.WebHandle; +export default function(): wasm_bindgen; diff --git a/apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer.nodedc.js b/apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer.nodedc.js new file mode 100644 index 0000000..3b63e8d --- /dev/null +++ b/apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer.nodedc.js @@ -0,0 +1,4754 @@ + +export default function() { +const exports = { __proto__: null }; + + let script_src; + if (typeof document !== 'undefined' && document.currentScript !== null) { + script_src = new URL(document.currentScript.src, location.href).toString(); + } + + class IntoUnderlyingByteSource { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + IntoUnderlyingByteSourceFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_intounderlyingbytesource_free(ptr, 0); + } + /** + * @returns {number} + */ + get autoAllocateChunkSize() { + const ret = wasm.intounderlyingbytesource_autoAllocateChunkSize(this.__wbg_ptr); + return ret >>> 0; + } + cancel() { + const ptr = this.__destroy_into_raw(); + wasm.intounderlyingbytesource_cancel(ptr); + } + /** + * @param {ReadableByteStreamController} controller + * @returns {Promise} + */ + pull(controller) { + const ret = wasm.intounderlyingbytesource_pull(this.__wbg_ptr, controller); + return ret; + } + /** + * @param {ReadableByteStreamController} controller + */ + start(controller) { + wasm.intounderlyingbytesource_start(this.__wbg_ptr, controller); + } + /** + * @returns {ReadableStreamType} + */ + get type() { + const ret = wasm.intounderlyingbytesource_type(this.__wbg_ptr); + return __wbindgen_enum_ReadableStreamType[ret]; + } + } + if (Symbol.dispose) IntoUnderlyingByteSource.prototype[Symbol.dispose] = IntoUnderlyingByteSource.prototype.free; + exports.IntoUnderlyingByteSource = IntoUnderlyingByteSource; + + class IntoUnderlyingSink { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + IntoUnderlyingSinkFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_intounderlyingsink_free(ptr, 0); + } + /** + * @param {any} reason + * @returns {Promise} + */ + abort(reason) { + const ptr = this.__destroy_into_raw(); + const ret = wasm.intounderlyingsink_abort(ptr, reason); + return ret; + } + /** + * @returns {Promise} + */ + close() { + const ptr = this.__destroy_into_raw(); + const ret = wasm.intounderlyingsink_close(ptr); + return ret; + } + /** + * @param {any} chunk + * @returns {Promise} + */ + write(chunk) { + const ret = wasm.intounderlyingsink_write(this.__wbg_ptr, chunk); + return ret; + } + } + if (Symbol.dispose) IntoUnderlyingSink.prototype[Symbol.dispose] = IntoUnderlyingSink.prototype.free; + exports.IntoUnderlyingSink = IntoUnderlyingSink; + + class IntoUnderlyingSource { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + IntoUnderlyingSourceFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_intounderlyingsource_free(ptr, 0); + } + cancel() { + const ptr = this.__destroy_into_raw(); + wasm.intounderlyingsource_cancel(ptr); + } + /** + * @param {ReadableStreamDefaultController} controller + * @returns {Promise} + */ + pull(controller) { + const ret = wasm.intounderlyingsource_pull(this.__wbg_ptr, controller); + return ret; + } + } + if (Symbol.dispose) IntoUnderlyingSource.prototype[Symbol.dispose] = IntoUnderlyingSource.prototype.free; + exports.IntoUnderlyingSource = IntoUnderlyingSource; + + class WebHandle { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + WebHandleFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_webhandle_free(ptr, 0); + } + /** + * Add a new receiver streaming data from the given url. + * + * Websocket streams are always opened in `Following` mode. + * + * It is an error to open a channel twice with the same id. + * @param {string} url + */ + add_receiver(url) { + const ptr0 = passStringToWasm0(url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + wasm.webhandle_add_receiver(this.__wbg_ptr, ptr0, len0); + } + /** + * Close an existing channel for streaming data. + * + * No-op if the channel is already closed. + * @param {string} id + */ + close_channel(id) { + const ptr0 = passStringToWasm0(id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + wasm.webhandle_close_channel(this.__wbg_ptr, ptr0, len0); + } + destroy() { + wasm.webhandle_destroy(this.__wbg_ptr); + } + /** + * @returns {string | undefined} + */ + get_active_recording_id() { + const ret = wasm.webhandle_get_active_recording_id(this.__wbg_ptr); + let v1; + if (ret[0] !== 0) { + v1 = getStringFromWasm0(ret[0], ret[1]).slice(); + wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); + } + return v1; + } + /** + * @param {string} recording_id + * @returns {string | undefined} + */ + get_active_timeline(recording_id) { + const ptr0 = passStringToWasm0(recording_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.webhandle_get_active_timeline(this.__wbg_ptr, ptr0, len0); + let v2; + if (ret[0] !== 0) { + v2 = getStringFromWasm0(ret[0], ret[1]).slice(); + wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); + } + return v2; + } + /** + * @param {string} recording_id + * @returns {boolean | undefined} + */ + get_playing(recording_id) { + const ptr0 = passStringToWasm0(recording_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.webhandle_get_playing(this.__wbg_ptr, ptr0, len0); + return ret === 0xFFFFFF ? undefined : ret !== 0; + } + /** + * @param {string} recording_id + * @param {string} timeline_name + * @returns {number | undefined} + */ + get_time_for_timeline(recording_id, timeline_name) { + const ptr0 = passStringToWasm0(recording_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(timeline_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.webhandle_get_time_for_timeline(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret[0] === 0 ? undefined : ret[1]; + } + /** + * @param {string} recording_id + * @param {string} timeline_name + * @returns {any} + */ + get_timeline_time_range(recording_id, timeline_name) { + const ptr0 = passStringToWasm0(recording_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(timeline_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.webhandle_get_timeline_time_range(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret; + } + /** + * @returns {boolean} + */ + has_panicked() { + const ret = wasm.webhandle_has_panicked(this.__wbg_ptr); + return ret !== 0; + } + /** + * @param {any} app_options + */ + constructor(app_options) { + const ret = wasm.webhandle_new(app_options); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + this.__wbg_ptr = ret[0]; + WebHandleFinalization.register(this, this.__wbg_ptr, this); + return this; + } + /** + * NODE.DC navigation/v1. Last rendered native eye, never a replay of DOM deltas. + * Each Mission Core iframe admits one active 3D pane. Timestamp selection + * excludes states retained for a previously selected/reset pane. + * @returns {string | undefined} + */ + nodedc_camera_eye() { + const ret = wasm.webhandle_nodedc_camera_eye(this.__wbg_ptr); + let v1; + if (ret[0] !== 0) { + v1 = getStringFromWasm0(ret[0], ret[1]).slice(); + wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); + } + return v1; + } + /** + * Open a new channel for streaming data. + * + * It is an error to open a channel twice with the same id. + * @param {string} id + * @param {string} channel_name + */ + open_channel(id, channel_name) { + const ptr0 = passStringToWasm0(id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(channel_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + wasm.webhandle_open_channel(this.__wbg_ptr, ptr0, len0, ptr1, len1); + } + /** + * @param {string} panel + * @param {string | null} [state] + */ + override_panel_state(panel, state) { + const ptr0 = passStringToWasm0(panel, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + var ptr1 = isLikeNone(state) ? 0 : passStringToWasm0(state, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + const ret = wasm.webhandle_override_panel_state(this.__wbg_ptr, ptr0, len0, ptr1, len1); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + /** + * @returns {string | undefined} + */ + panic_callstack() { + const ret = wasm.webhandle_panic_callstack(this.__wbg_ptr); + let v1; + if (ret[0] !== 0) { + v1 = getStringFromWasm0(ret[0], ret[1]).slice(); + wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); + } + return v1; + } + /** + * @returns {string | undefined} + */ + panic_message() { + const ret = wasm.webhandle_panic_message(this.__wbg_ptr); + let v1; + if (ret[0] !== 0) { + v1 = getStringFromWasm0(ret[0], ret[1]).slice(); + wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); + } + return v1; + } + /** + * @param {string} url + */ + remove_receiver(url) { + const ptr0 = passStringToWasm0(url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + wasm.webhandle_remove_receiver(this.__wbg_ptr, ptr0, len0); + } + /** + * Add an rrd to the viewer directly from a byte array. + * @param {string} id + * @param {Uint8Array} data + */ + send_rrd_to_channel(id, data) { + const ptr0 = passStringToWasm0(id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passArray8ToWasm0(data, wasm.__wbindgen_malloc); + const len1 = WASM_VECTOR_LEN; + wasm.webhandle_send_rrd_to_channel(this.__wbg_ptr, ptr0, len0, ptr1, len1); + } + /** + * @param {string} id + * @param {Uint8Array} data + */ + send_table_to_channel(id, data) { + const ptr0 = passStringToWasm0(id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passArray8ToWasm0(data, wasm.__wbindgen_malloc); + const len1 = WASM_VECTOR_LEN; + wasm.webhandle_send_table_to_channel(this.__wbg_ptr, ptr0, len0, ptr1, len1); + } + /** + * @param {string} recording_id + */ + set_active_recording_id(recording_id) { + const ptr0 = passStringToWasm0(recording_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + wasm.webhandle_set_active_recording_id(this.__wbg_ptr, ptr0, len0); + } + /** + * Set the active timeline. + * + * This does nothing if the timeline can't be found. + * @param {string} recording_id + * @param {string} timeline_name + */ + set_active_timeline(recording_id, timeline_name) { + const ptr0 = passStringToWasm0(recording_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(timeline_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + wasm.webhandle_set_active_timeline(this.__wbg_ptr, ptr0, len0, ptr1, len1); + } + /** + * @param {string} access_token + * @param {string} email + */ + set_credentials(access_token, email) { + const ptr0 = passStringToWasm0(access_token, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(email, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + wasm.webhandle_set_credentials(this.__wbg_ptr, ptr0, len0, ptr1, len1); + } + /** + * @param {string} recording_id + * @param {boolean} value + */ + set_playing(recording_id, value) { + const ptr0 = passStringToWasm0(recording_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + wasm.webhandle_set_playing(this.__wbg_ptr, ptr0, len0, value); + } + /** + * @param {string} recording_id + * @param {string} timeline_name + * @param {number} time + */ + set_time_for_timeline(recording_id, timeline_name, time) { + const ptr0 = passStringToWasm0(recording_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(timeline_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + wasm.webhandle_set_time_for_timeline(this.__wbg_ptr, ptr0, len0, ptr1, len1, time); + } + /** + * @param {any} canvas + * @returns {Promise} + */ + start(canvas) { + const ret = wasm.webhandle_start(this.__wbg_ptr, canvas); + return ret; + } + /** + * @param {boolean | null} [value] + */ + toggle_panel_overrides(value) { + wasm.webhandle_toggle_panel_overrides(this.__wbg_ptr, isLikeNone(value) ? 0xFFFFFF : value ? 1 : 0); + } + } + if (Symbol.dispose) WebHandle.prototype[Symbol.dispose] = WebHandle.prototype.free; + exports.WebHandle = WebHandle; + function __wbg_get_imports() { + const import0 = { + __proto__: null, + __wbg_Error_92b29b0548f8b746: function(arg0, arg1) { + const ret = Error(getStringFromWasm0(arg0, arg1)); + return ret; + }, + __wbg_Number_9a4e0ecb0fa16705: function(arg0) { + const ret = Number(arg0); + return ret; + }, + __wbg_Window_afcc911b2f9c92e2: function(arg0) { + const ret = arg0.Window; + return ret; + }, + __wbg_WorkerGlobalScope_5d19ebc889ff397e: function(arg0) { + const ret = arg0.WorkerGlobalScope; + return ret; + }, + __wbg___wbindgen_boolean_get_fa956cfa2d1bd751: function(arg0) { + const v = arg0; + const ret = typeof(v) === 'boolean' ? v : undefined; + return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0; + }, + __wbg___wbindgen_debug_string_c25d447a39f5578f: function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg___wbindgen_in_aca499c5de7ff5e5: function(arg0, arg1) { + const ret = arg0 in arg1; + return ret; + }, + __wbg___wbindgen_is_falsy_a6dfe792ff282f10: function(arg0) { + const ret = !arg0; + return ret; + }, + __wbg___wbindgen_is_function_1ff95bcc5517c252: function(arg0) { + const ret = typeof(arg0) === 'function'; + return ret; + }, + __wbg___wbindgen_is_null_ea9085d691f535d3: function(arg0) { + const ret = arg0 === null; + return ret; + }, + __wbg___wbindgen_is_object_a27215656b807791: function(arg0) { + const val = arg0; + const ret = typeof(val) === 'object' && val !== null; + return ret; + }, + __wbg___wbindgen_is_string_ea5e6cc2e4141dfe: function(arg0) { + const ret = typeof(arg0) === 'string'; + return ret; + }, + __wbg___wbindgen_is_undefined_c05833b95a3cf397: function(arg0) { + const ret = arg0 === undefined; + return ret; + }, + __wbg___wbindgen_jsval_loose_eq_db4c3b15f63fc170: function(arg0, arg1) { + const ret = arg0 == arg1; + return ret; + }, + __wbg___wbindgen_number_get_394265ed1e1b84ee: function(arg0, arg1) { + const obj = arg1; + const ret = typeof(obj) === 'number' ? obj : undefined; + getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true); + }, + __wbg___wbindgen_string_get_b0ca35b86a603356: function(arg0, arg1) { + const obj = arg1; + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg___wbindgen_throw_344f42d3211c4765: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbg__wbg_cb_unref_fffb441def202758: function(arg0) { + arg0._wbg_cb_unref(); + }, + __wbg_abort_807c9d22f53767f5: function(arg0) { + const ret = arg0.abort(); + return ret; + }, + __wbg_abort_8bae0f33e7833997: function(arg0) { + arg0.abort(); + }, + __wbg_abort_eee9248a6d680839: function(arg0, arg1) { + arg0.abort(arg1); + }, + __wbg_activeElement_4bc99dc1a7094c27: function(arg0) { + const ret = arg0.activeElement; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_activeElement_b85d218c5f49326e: function(arg0) { + const ret = arg0.activeElement; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_activeTexture_92b04d918019d603: function(arg0, arg1) { + arg0.activeTexture(arg1 >>> 0); + }, + __wbg_activeTexture_d12958674e97a118: function(arg0, arg1) { + arg0.activeTexture(arg1 >>> 0); + }, + __wbg_addEventListener_520e749bbae24529: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + arg0.addEventListener(getStringFromWasm0(arg1, arg2), arg3, arg4); + }, arguments); }, + __wbg_addEventListener_d85450ee1320c989: function() { return handleError(function (arg0, arg1, arg2, arg3) { + arg0.addEventListener(getStringFromWasm0(arg1, arg2), arg3); + }, arguments); }, + __wbg_altKey_50f830d1793a2eea: function(arg0) { + const ret = arg0.altKey; + return ret; + }, + __wbg_altKey_f3e24c4c9cfcf271: function(arg0) { + const ret = arg0.altKey; + return ret; + }, + __wbg_appendChild_f553e8704c4f14a6: function() { return handleError(function (arg0, arg1) { + const ret = arg0.appendChild(arg1); + return ret; + }, arguments); }, + __wbg_append_01c74e5c6b58aa64: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + arg0.append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, arguments); }, + __wbg_append_1e60bd927c609246: function(arg0, arg1, arg2, arg3, arg4) { + arg0.append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, + __wbg_arrayBuffer_3b637f0fa65c5351: function() { return handleError(function (arg0) { + const ret = arg0.arrayBuffer(); + return ret; + }, arguments); }, + __wbg_arrayBuffer_a158e423a87ee756: function(arg0) { + const ret = arg0.arrayBuffer(); + return ret; + }, + __wbg_assign_7e262bdaf75bb707: function() { return handleError(function (arg0, arg1, arg2) { + arg0.assign(getStringFromWasm0(arg1, arg2)); + }, arguments); }, + __wbg_at_031a0b72e465b17e: function(arg0, arg1) { + const ret = arg0.at(arg1); + return ret; + }, + __wbg_attachShader_5f7f4077e124e23b: function(arg0, arg1, arg2) { + arg0.attachShader(arg1, arg2); + }, + __wbg_attachShader_8971266b4c9bc514: function(arg0, arg1, arg2) { + arg0.attachShader(arg1, arg2); + }, + __wbg_back_939cbdbdfad8aff7: function() { return handleError(function (arg0) { + arg0.back(); + }, arguments); }, + __wbg_beginQuery_042a1f99e870066c: function(arg0, arg1, arg2) { + arg0.beginQuery(arg1 >>> 0, arg2); + }, + __wbg_beginRenderPass_aa22c432e793359a: function() { return handleError(function (arg0, arg1) { + const ret = arg0.beginRenderPass(arg1); + return ret; + }, arguments); }, + __wbg_bindAttribLocation_0fe5da7e01ac0d15: function(arg0, arg1, arg2, arg3, arg4) { + arg0.bindAttribLocation(arg1, arg2 >>> 0, getStringFromWasm0(arg3, arg4)); + }, + __wbg_bindAttribLocation_94202d7a59ab7863: function(arg0, arg1, arg2, arg3, arg4) { + arg0.bindAttribLocation(arg1, arg2 >>> 0, getStringFromWasm0(arg3, arg4)); + }, + __wbg_bindBufferRange_f5c29912db0476e9: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.bindBufferRange(arg1 >>> 0, arg2 >>> 0, arg3, arg4, arg5); + }, + __wbg_bindBuffer_1e00cfb4321ef9a4: function(arg0, arg1, arg2) { + arg0.bindBuffer(arg1 >>> 0, arg2); + }, + __wbg_bindBuffer_a01497b1abdcdd9a: function(arg0, arg1, arg2) { + arg0.bindBuffer(arg1 >>> 0, arg2); + }, + __wbg_bindFramebuffer_390311eff3896937: function(arg0, arg1, arg2) { + arg0.bindFramebuffer(arg1 >>> 0, arg2); + }, + __wbg_bindFramebuffer_658e4b06f7ee8bb4: function(arg0, arg1, arg2) { + arg0.bindFramebuffer(arg1 >>> 0, arg2); + }, + __wbg_bindRenderbuffer_75e8469e930840fa: function(arg0, arg1, arg2) { + arg0.bindRenderbuffer(arg1 >>> 0, arg2); + }, + __wbg_bindRenderbuffer_c3d0c4b8cd1c3891: function(arg0, arg1, arg2) { + arg0.bindRenderbuffer(arg1 >>> 0, arg2); + }, + __wbg_bindSampler_ce608f0de9d31acf: function(arg0, arg1, arg2) { + arg0.bindSampler(arg1 >>> 0, arg2); + }, + __wbg_bindTexture_28eff4bbd8aaab54: function(arg0, arg1, arg2) { + arg0.bindTexture(arg1 >>> 0, arg2); + }, + __wbg_bindTexture_9b04b1b7c00d4dd6: function(arg0, arg1, arg2) { + arg0.bindTexture(arg1 >>> 0, arg2); + }, + __wbg_bindVertexArrayOES_5cad2205a17e8990: function(arg0, arg1) { + arg0.bindVertexArrayOES(arg1); + }, + __wbg_bindVertexArray_427eeac0c1764d8a: function(arg0, arg1) { + arg0.bindVertexArray(arg1); + }, + __wbg_blendColor_793b560dc69ddd0b: function(arg0, arg1, arg2, arg3, arg4) { + arg0.blendColor(arg1, arg2, arg3, arg4); + }, + __wbg_blendColor_eae0cd578a2c7d15: function(arg0, arg1, arg2, arg3, arg4) { + arg0.blendColor(arg1, arg2, arg3, arg4); + }, + __wbg_blendEquationSeparate_043e2f50f6ecb2d3: function(arg0, arg1, arg2) { + arg0.blendEquationSeparate(arg1 >>> 0, arg2 >>> 0); + }, + __wbg_blendEquationSeparate_c7e2b2261c94e1c5: function(arg0, arg1, arg2) { + arg0.blendEquationSeparate(arg1 >>> 0, arg2 >>> 0); + }, + __wbg_blendEquation_455b8986ededabc0: function(arg0, arg1) { + arg0.blendEquation(arg1 >>> 0); + }, + __wbg_blendEquation_f5c5272993f6cb01: function(arg0, arg1) { + arg0.blendEquation(arg1 >>> 0); + }, + __wbg_blendFuncSeparate_37156309688f8f88: function(arg0, arg1, arg2, arg3, arg4) { + arg0.blendFuncSeparate(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4 >>> 0); + }, + __wbg_blendFuncSeparate_3ee6d939a9f3938b: function(arg0, arg1, arg2, arg3, arg4) { + arg0.blendFuncSeparate(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4 >>> 0); + }, + __wbg_blendFunc_114dc7056ccfeb8d: function(arg0, arg1, arg2) { + arg0.blendFunc(arg1 >>> 0, arg2 >>> 0); + }, + __wbg_blendFunc_a854d7e4459150ba: function(arg0, arg1, arg2) { + arg0.blendFunc(arg1 >>> 0, arg2 >>> 0); + }, + __wbg_blitFramebuffer_a1215976f663b058: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) { + arg0.blitFramebuffer(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10 >>> 0); + }, + __wbg_blockSize_5af477b962b2b031: function(arg0) { + const ret = arg0.blockSize; + return ret; + }, + __wbg_blur_e902dcc79406e89c: function() { return handleError(function (arg0) { + arg0.blur(); + }, arguments); }, + __wbg_body_18c9f2ac15ead4b2: function(arg0) { + const ret = arg0.body; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_body_40ec34e0a2931fe8: function(arg0) { + const ret = arg0.body; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_bottom_e6ed49b80d965dae: function(arg0) { + const ret = arg0.bottom; + return ret; + }, + __wbg_bufferData_073a7c6abef7a55f: function(arg0, arg1, arg2, arg3) { + arg0.bufferData(arg1 >>> 0, arg2, arg3 >>> 0); + }, + __wbg_bufferData_3d4f29bdfb1fa46c: function(arg0, arg1, arg2, arg3) { + arg0.bufferData(arg1 >>> 0, arg2, arg3 >>> 0); + }, + __wbg_bufferData_90ef588bac2be2f5: function(arg0, arg1, arg2, arg3) { + arg0.bufferData(arg1 >>> 0, arg2, arg3 >>> 0); + }, + __wbg_bufferData_ce4f44d56e9ddab5: function(arg0, arg1, arg2, arg3) { + arg0.bufferData(arg1 >>> 0, arg2, arg3 >>> 0); + }, + __wbg_bufferSubData_bae930b21e9c1c48: function(arg0, arg1, arg2, arg3) { + arg0.bufferSubData(arg1 >>> 0, arg2, arg3); + }, + __wbg_bufferSubData_ce9854d3d337e2cf: function(arg0, arg1, arg2, arg3) { + arg0.bufferSubData(arg1 >>> 0, arg2, arg3); + }, + __wbg_buffer_54b87055582c8a81: function(arg0) { + const ret = arg0.buffer; + return ret; + }, + __wbg_button_f6a9a7b725f1838e: function(arg0) { + const ret = arg0.button; + return ret; + }, + __wbg_byobRequest_06b654bb15590436: function(arg0) { + const ret = arg0.byobRequest; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_byteLength_41862ca4020b9c43: function(arg0) { + const ret = arg0.byteLength; + return ret; + }, + __wbg_byteOffset_d42e18c4441f628b: function(arg0) { + const ret = arg0.byteOffset; + return ret; + }, + __wbg_call_8a2dd23819f8a60a: function() { return handleError(function (arg0, arg1) { + const ret = arg0.call(arg1); + return ret; + }, arguments); }, + __wbg_call_a6e5c5dce5018821: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.call(arg1, arg2); + return ret; + }, arguments); }, + __wbg_cancelAnimationFrame_086d6084925c4e06: function() { return handleError(function (arg0, arg1) { + arg0.cancelAnimationFrame(arg1); + }, arguments); }, + __wbg_cancel_3983a93e24cc66b3: function(arg0) { + const ret = arg0.cancel(); + return ret; + }, + __wbg_catch_c1a60df4c30d76d3: function(arg0, arg1) { + const ret = arg0.catch(arg1); + return ret; + }, + __wbg_changedTouches_dbf6eeabddd3c2da: function(arg0) { + const ret = arg0.changedTouches; + return ret; + }, + __wbg_clearBufferfv_2e0f1a0ea56de859: function(arg0, arg1, arg2, arg3, arg4) { + arg0.clearBufferfv(arg1 >>> 0, arg2, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_clearBufferiv_0360269bf6e34c54: function(arg0, arg1, arg2, arg3, arg4) { + arg0.clearBufferiv(arg1 >>> 0, arg2, getArrayI32FromWasm0(arg3, arg4)); + }, + __wbg_clearBufferuiv_df94a395d4915377: function(arg0, arg1, arg2, arg3, arg4) { + arg0.clearBufferuiv(arg1 >>> 0, arg2, getArrayU32FromWasm0(arg3, arg4)); + }, + __wbg_clearDepth_8b5d226aae155082: function(arg0, arg1) { + arg0.clearDepth(arg1); + }, + __wbg_clearDepth_ca9b22d41551b513: function(arg0, arg1) { + arg0.clearDepth(arg1); + }, + __wbg_clearInterval_2e2069e95ad09d4f: function(arg0, arg1) { + arg0.clearInterval(arg1); + }, + __wbg_clearStencil_58f2af46612bccae: function(arg0, arg1) { + arg0.clearStencil(arg1); + }, + __wbg_clearStencil_a66fe23df6313fc7: function(arg0, arg1) { + arg0.clearStencil(arg1); + }, + __wbg_clearTimeout_6b8d9a38b9263d65: function(arg0) { + const ret = clearTimeout(arg0); + return ret; + }, + __wbg_clearTimeout_8f80437be2324e09: function(arg0, arg1) { + arg0.clearTimeout(arg1); + }, + __wbg_clearTimeout_ef38e23d3d8f7baf: function(arg0) { + const ret = clearTimeout(arg0); + return ret; + }, + __wbg_clear_53d71d234e14e4c1: function(arg0, arg1) { + arg0.clear(arg1 >>> 0); + }, + __wbg_clear_dd06a0da4ce8e13f: function(arg0, arg1) { + arg0.clear(arg1 >>> 0); + }, + __wbg_click_22281da934e153f5: function(arg0) { + arg0.click(); + }, + __wbg_clientWaitSync_cf8e49f8ba228377: function(arg0, arg1, arg2, arg3) { + const ret = arg0.clientWaitSync(arg1, arg2 >>> 0, arg3 >>> 0); + return ret; + }, + __wbg_clientX_c396b0fb11d601d3: function(arg0) { + const ret = arg0.clientX; + return ret; + }, + __wbg_clientX_e8c6c674634344de: function(arg0) { + const ret = arg0.clientX; + return ret; + }, + __wbg_clientY_a4650836fdf58f01: function(arg0) { + const ret = arg0.clientY; + return ret; + }, + __wbg_clientY_ffea953797502d5d: function(arg0) { + const ret = arg0.clientY; + return ret; + }, + __wbg_clipboardData_b99e24e2d915217c: function(arg0) { + const ret = arg0.clipboardData; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_clipboard_cc7335fcba1a9a80: function(arg0) { + const ret = arg0.clipboard; + return ret; + }, + __wbg_close_1f3dc5faf2043944: function() { return handleError(function (arg0) { + arg0.close(); + }, arguments); }, + __wbg_close_249a23304523681b: function() { return handleError(function (arg0) { + arg0.close(); + }, arguments); }, + __wbg_close_72d318d9c16e83ef: function() { return handleError(function (arg0) { + arg0.close(); + }, arguments); }, + __wbg_close_90cc288ec0e6eb50: function() { return handleError(function (arg0) { + arg0.close(); + }, arguments); }, + __wbg_close_adb3a7073894d04e: function(arg0) { + arg0.close(); + }, + __wbg_closed_a9e0ba86bfa233c0: function() { return handleError(function (arg0) { + const ret = arg0.closed; + return ret; + }, arguments); }, + __wbg_code_89c999e407c79eef: function(arg0, arg1) { + const ret = arg1.code; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_code_cb4327cfc515673b: function(arg0) { + const ret = arg0.code; + return ret; + }, + __wbg_colorMask_44ebb91cad2502f2: function(arg0, arg1, arg2, arg3, arg4) { + arg0.colorMask(arg1 !== 0, arg2 !== 0, arg3 !== 0, arg4 !== 0); + }, + __wbg_colorMask_a4d164c2039b5731: function(arg0, arg1, arg2, arg3, arg4) { + arg0.colorMask(arg1 !== 0, arg2 !== 0, arg3 !== 0, arg4 !== 0); + }, + __wbg_compileShader_9bdfd792722cf704: function(arg0, arg1) { + arg0.compileShader(arg1); + }, + __wbg_compileShader_fc2e4b73240d4fd7: function(arg0, arg1) { + arg0.compileShader(arg1); + }, + __wbg_compressedTexSubImage2D_c1362291573c7268: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.compressedTexSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8, arg9); + }, + __wbg_compressedTexSubImage2D_da01674d2975d1ae: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { + arg0.compressedTexSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8); + }, + __wbg_compressedTexSubImage2D_dd6dc580749eb5cf: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { + arg0.compressedTexSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8); + }, + __wbg_compressedTexSubImage3D_04cb8b046c4321fe: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.compressedTexSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10, arg11); + }, + __wbg_compressedTexSubImage3D_af0228a80ffd5993: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) { + arg0.compressedTexSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10); + }, + __wbg_configure_0e4789c0f6b35c8e: function() { return handleError(function (arg0, arg1) { + arg0.configure(arg1); + }, arguments); }, + __wbg_configure_84a31356970a404e: function() { return handleError(function (arg0, arg1) { + arg0.configure(arg1); + }, arguments); }, + __wbg_contentBoxSize_74fbbc51859ff90e: function(arg0) { + const ret = arg0.contentBoxSize; + return ret; + }, + __wbg_contentRect_1d6e15e2e0d3e3c3: function(arg0) { + const ret = arg0.contentRect; + return ret; + }, + __wbg_copyBufferSubData_cdf61f74aa6e0902: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.copyBufferSubData(arg1 >>> 0, arg2 >>> 0, arg3, arg4, arg5); + }, + __wbg_copyBufferToBuffer_5e2cd8f10ae78183: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + arg0.copyBufferToBuffer(arg1, arg2, arg3, arg4); + }, arguments); }, + __wbg_copyBufferToBuffer_ca30deb8de65f5d5: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.copyBufferToBuffer(arg1, arg2, arg3, arg4, arg5); + }, arguments); }, + __wbg_copyBufferToTexture_29b9d26780296819: function() { return handleError(function (arg0, arg1, arg2, arg3) { + arg0.copyBufferToTexture(arg1, arg2, arg3); + }, arguments); }, + __wbg_copyExternalImageToTexture_4df105bb39517948: function() { return handleError(function (arg0, arg1, arg2, arg3) { + arg0.copyExternalImageToTexture(arg1, arg2, arg3); + }, arguments); }, + __wbg_copyTexSubImage2D_8daea651fc408645: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { + arg0.copyTexSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + }, + __wbg_copyTexSubImage2D_c73f91f1d7022402: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { + arg0.copyTexSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + }, + __wbg_copyTexSubImage3D_bfe7a14dac9ad777: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.copyTexSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); + }, + __wbg_copyTextureToBuffer_ed6e67a77ecb768d: function() { return handleError(function (arg0, arg1, arg2, arg3) { + arg0.copyTextureToBuffer(arg1, arg2, arg3); + }, arguments); }, + __wbg_createBindGroupLayout_49a7e2b3d076afcf: function() { return handleError(function (arg0, arg1) { + const ret = arg0.createBindGroupLayout(arg1); + return ret; + }, arguments); }, + __wbg_createBindGroup_655c6e6c0258530e: function(arg0, arg1) { + const ret = arg0.createBindGroup(arg1); + return ret; + }, + __wbg_createBuffer_01568a9d930d90dd: function(arg0) { + const ret = arg0.createBuffer(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createBuffer_0726dd2ab09ea1d2: function() { return handleError(function (arg0, arg1) { + const ret = arg0.createBuffer(arg1); + return ret; + }, arguments); }, + __wbg_createBuffer_2075765bde5035d5: function(arg0) { + const ret = arg0.createBuffer(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createCommandEncoder_ec1f40f0cb4d09df: function(arg0, arg1) { + const ret = arg0.createCommandEncoder(arg1); + return ret; + }, + __wbg_createElement_fcbc0805de826d62: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.createElement(getStringFromWasm0(arg1, arg2)); + return ret; + }, arguments); }, + __wbg_createFramebuffer_b24d2c80a8b9e7cc: function(arg0) { + const ret = arg0.createFramebuffer(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createFramebuffer_de0d521f546e7534: function(arg0) { + const ret = arg0.createFramebuffer(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createImageBitmap_5dbcdc6d11d377bd: function() { return handleError(function (arg0, arg1) { + const ret = arg0.createImageBitmap(arg1); + return ret; + }, arguments); }, + __wbg_createObjectURL_416e527781e6fd6d: function() { return handleError(function (arg0, arg1) { + const ret = URL.createObjectURL(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_createPipelineLayout_2c8cd4528b06c108: function(arg0, arg1) { + const ret = arg0.createPipelineLayout(arg1); + return ret; + }, + __wbg_createProgram_118becaac3a20318: function(arg0) { + const ret = arg0.createProgram(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createProgram_538c9777a4ac084f: function(arg0) { + const ret = arg0.createProgram(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createQuery_047c7c524e4ac4f8: function(arg0) { + const ret = arg0.createQuery(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createRenderPipeline_cf98d4d699bfb03c: function() { return handleError(function (arg0, arg1) { + const ret = arg0.createRenderPipeline(arg1); + return ret; + }, arguments); }, + __wbg_createRenderbuffer_71af5c0d615e9271: function(arg0) { + const ret = arg0.createRenderbuffer(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createRenderbuffer_9d801bf44c314f44: function(arg0) { + const ret = arg0.createRenderbuffer(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createSampler_70c8392d98896235: function(arg0) { + const ret = arg0.createSampler(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createSampler_c8ffb3c8d565f704: function(arg0, arg1) { + const ret = arg0.createSampler(arg1); + return ret; + }, + __wbg_createShaderModule_2e44fc7677c6288b: function(arg0, arg1) { + const ret = arg0.createShaderModule(arg1); + return ret; + }, + __wbg_createShader_78bc8b7e9a88e1a8: function(arg0, arg1) { + const ret = arg0.createShader(arg1 >>> 0); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createShader_7d139f2d50f77365: function(arg0, arg1) { + const ret = arg0.createShader(arg1 >>> 0); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createTexture_0ee0fa5f924f3d14: function(arg0) { + const ret = arg0.createTexture(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createTexture_1bac74c999b8a48e: function() { return handleError(function (arg0, arg1) { + const ret = arg0.createTexture(arg1); + return ret; + }, arguments); }, + __wbg_createTexture_d13f98e0d3d912f4: function(arg0) { + const ret = arg0.createTexture(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createVertexArrayOES_2fa3e59eebd5f674: function(arg0) { + const ret = arg0.createVertexArrayOES(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createVertexArray_baf9eef7ea5a2c7a: function(arg0) { + const ret = arg0.createVertexArray(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_createView_ceaf2f5881adbd34: function() { return handleError(function (arg0, arg1) { + const ret = arg0.createView(arg1); + return ret; + }, arguments); }, + __wbg_createWritable_659288d5245034d3: function(arg0) { + const ret = arg0.createWritable(); + return ret; + }, + __wbg_ctrlKey_2e52816fa7160097: function(arg0) { + const ret = arg0.ctrlKey; + return ret; + }, + __wbg_ctrlKey_50bd8324959ca786: function(arg0) { + const ret = arg0.ctrlKey; + return ret; + }, + __wbg_cullFace_62bbea3bef0e6b99: function(arg0, arg1) { + arg0.cullFace(arg1 >>> 0); + }, + __wbg_cullFace_f1c75ae19b07eaf3: function(arg0, arg1) { + arg0.cullFace(arg1 >>> 0); + }, + __wbg_dataTransfer_c1c4745cee7e05f1: function(arg0) { + const ret = arg0.dataTransfer; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_data_328de4280640da92: function(arg0) { + const ret = arg0.data; + return ret; + }, + __wbg_debug_87fd9b1a625b7efb: function(arg0) { + console.debug(arg0); + }, + __wbg_debug_cf53e2612c84e4ee: function(arg0, arg1, arg2, arg3) { + console.debug(arg0, arg1, arg2, arg3); + }, + __wbg_decode_02f9ccfc83b87859: function() { return handleError(function (arg0, arg1) { + arg0.decode(arg1); + }, arguments); }, + __wbg_deleteBuffer_08eb938e35c27967: function(arg0, arg1) { + arg0.deleteBuffer(arg1); + }, + __wbg_deleteBuffer_1ca3ffe668a488e7: function(arg0, arg1) { + arg0.deleteBuffer(arg1); + }, + __wbg_deleteFramebuffer_963cd69957209d37: function(arg0, arg1) { + arg0.deleteFramebuffer(arg1); + }, + __wbg_deleteFramebuffer_d1a36e889b009344: function(arg0, arg1) { + arg0.deleteFramebuffer(arg1); + }, + __wbg_deleteProgram_09bd45a51105b2f6: function(arg0, arg1) { + arg0.deleteProgram(arg1); + }, + __wbg_deleteProgram_132e191baa9fa84f: function(arg0, arg1) { + arg0.deleteProgram(arg1); + }, + __wbg_deleteQuery_0d1dcc4402a86ee1: function(arg0, arg1) { + arg0.deleteQuery(arg1); + }, + __wbg_deleteRenderbuffer_52bdbf5ab2cbe62a: function(arg0, arg1) { + arg0.deleteRenderbuffer(arg1); + }, + __wbg_deleteRenderbuffer_ca999f7883b777af: function(arg0, arg1) { + arg0.deleteRenderbuffer(arg1); + }, + __wbg_deleteSampler_0abb528566c4ab3b: function(arg0, arg1) { + arg0.deleteSampler(arg1); + }, + __wbg_deleteShader_3120790d36063afe: function(arg0, arg1) { + arg0.deleteShader(arg1); + }, + __wbg_deleteShader_993edb4beb3c4d53: function(arg0, arg1) { + arg0.deleteShader(arg1); + }, + __wbg_deleteSync_9b0e43580942a0f6: function(arg0, arg1) { + arg0.deleteSync(arg1); + }, + __wbg_deleteTexture_2b163b157ea1be24: function(arg0, arg1) { + arg0.deleteTexture(arg1); + }, + __wbg_deleteTexture_bdc2202d7a50dcea: function(arg0, arg1) { + arg0.deleteTexture(arg1); + }, + __wbg_deleteVertexArrayOES_7fa59c32cfdfa6fa: function(arg0, arg1) { + arg0.deleteVertexArrayOES(arg1); + }, + __wbg_deleteVertexArray_475d4e969aac1dd0: function(arg0, arg1) { + arg0.deleteVertexArray(arg1); + }, + __wbg_delete_50c5af3bd629e1bf: function() { return handleError(function (arg0, arg1, arg2) { + delete arg0[getStringFromWasm0(arg1, arg2)]; + }, arguments); }, + __wbg_deltaMode_d869228efd74f393: function(arg0) { + const ret = arg0.deltaMode; + return ret; + }, + __wbg_deltaX_5d829ffba565ed10: function(arg0) { + const ret = arg0.deltaX; + return ret; + }, + __wbg_deltaY_6cfce8f8da250c23: function(arg0) { + const ret = arg0.deltaY; + return ret; + }, + __wbg_depthFunc_455cfeb8a9d2fb4c: function(arg0, arg1) { + arg0.depthFunc(arg1 >>> 0); + }, + __wbg_depthFunc_74a8f8acf8973c86: function(arg0, arg1) { + arg0.depthFunc(arg1 >>> 0); + }, + __wbg_depthMask_4bd6c73b1339d257: function(arg0, arg1) { + arg0.depthMask(arg1 !== 0); + }, + __wbg_depthMask_a644a67deced3257: function(arg0, arg1) { + arg0.depthMask(arg1 !== 0); + }, + __wbg_depthRange_38b2287ffbea14fd: function(arg0, arg1, arg2) { + arg0.depthRange(arg1, arg2); + }, + __wbg_depthRange_5e90d4d236280ff5: function(arg0, arg1, arg2) { + arg0.depthRange(arg1, arg2); + }, + __wbg_description_02485704e69b1e7f: function(arg0, arg1) { + const ret = arg1.description; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_destroy_7ae6d79c9c5ca8d9: function(arg0) { + arg0.destroy(); + }, + __wbg_destroy_fe937f756bf8df37: function(arg0) { + arg0.destroy(); + }, + __wbg_devicePixelContentBoxSize_dca8701a53307aca: function(arg0) { + const ret = arg0.devicePixelContentBoxSize; + return ret; + }, + __wbg_devicePixelRatio_1c0e0ed7deb19cd8: function(arg0) { + const ret = arg0.devicePixelRatio; + return ret; + }, + __wbg_disableVertexAttribArray_160060fbd7e97de0: function(arg0, arg1) { + arg0.disableVertexAttribArray(arg1 >>> 0); + }, + __wbg_disableVertexAttribArray_c7915eb0de6dd8f1: function(arg0, arg1) { + arg0.disableVertexAttribArray(arg1 >>> 0); + }, + __wbg_disable_1659d1b7d50c31e7: function(arg0, arg1) { + arg0.disable(arg1 >>> 0); + }, + __wbg_disable_40c3975167c1ee07: function(arg0, arg1) { + arg0.disable(arg1 >>> 0); + }, + __wbg_disconnect_39bfdcb35b1fc7b9: function(arg0) { + arg0.disconnect(); + }, + __wbg_displayHeight_f06554969d4d6de8: function(arg0) { + const ret = arg0.displayHeight; + return ret; + }, + __wbg_displayWidth_fdcc0a114d98d13e: function(arg0) { + const ret = arg0.displayWidth; + return ret; + }, + __wbg_document_179650d6cb13c263: function(arg0) { + const ret = arg0.document; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_done_89b2b13e91a60321: function(arg0) { + const ret = arg0.done; + return ret; + }, + __wbg_drawArraysInstancedANGLE_d58dbd2d38fdebaa: function(arg0, arg1, arg2, arg3, arg4) { + arg0.drawArraysInstancedANGLE(arg1 >>> 0, arg2, arg3, arg4); + }, + __wbg_drawArraysInstanced_51b161548a3f10c4: function(arg0, arg1, arg2, arg3, arg4) { + arg0.drawArraysInstanced(arg1 >>> 0, arg2, arg3, arg4); + }, + __wbg_drawArrays_676becae0149ed65: function(arg0, arg1, arg2, arg3) { + arg0.drawArrays(arg1 >>> 0, arg2, arg3); + }, + __wbg_drawArrays_b0c59a6e158122f2: function(arg0, arg1, arg2, arg3) { + arg0.drawArrays(arg1 >>> 0, arg2, arg3); + }, + __wbg_drawBuffersWEBGL_c9b47f7f207125cf: function(arg0, arg1) { + arg0.drawBuffersWEBGL(arg1); + }, + __wbg_drawBuffers_1c1ec9b292442a2a: function(arg0, arg1) { + arg0.drawBuffers(arg1); + }, + __wbg_drawElementsInstancedANGLE_9b58c4013373b180: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.drawElementsInstancedANGLE(arg1 >>> 0, arg2, arg3 >>> 0, arg4, arg5); + }, + __wbg_drawElementsInstanced_c7f96ea02e6d5326: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.drawElementsInstanced(arg1 >>> 0, arg2, arg3 >>> 0, arg4, arg5); + }, + __wbg_drawIndexed_d31913e79d58fbac: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.drawIndexed(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4, arg5 >>> 0); + }, + __wbg_draw_6877f98847e1e36c: function(arg0, arg1, arg2, arg3, arg4) { + arg0.draw(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4 >>> 0); + }, + __wbg_elementFromPoint_082557ad1c446761: function(arg0, arg1, arg2) { + const ret = arg0.elementFromPoint(arg1, arg2); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_elementFromPoint_49d780c87b3d05b7: function(arg0, arg1, arg2) { + const ret = arg0.elementFromPoint(arg1, arg2); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_enableVertexAttribArray_4c08219124740f14: function(arg0, arg1) { + arg0.enableVertexAttribArray(arg1 >>> 0); + }, + __wbg_enableVertexAttribArray_7470ba2dcf2606e3: function(arg0, arg1) { + arg0.enableVertexAttribArray(arg1 >>> 0); + }, + __wbg_enable_28bbeed576131d1f: function(arg0, arg1) { + arg0.enable(arg1 >>> 0); + }, + __wbg_enable_611804c0ac1504ce: function(arg0, arg1) { + arg0.enable(arg1 >>> 0); + }, + __wbg_endQuery_a50f7fc49cfe56e9: function(arg0, arg1) { + arg0.endQuery(arg1 >>> 0); + }, + __wbg_end_f99ebed53d4e198a: function(arg0) { + arg0.end(); + }, + __wbg_enqueue_6d83b4c6281bafd6: function() { return handleError(function (arg0, arg1) { + arg0.enqueue(arg1); + }, arguments); }, + __wbg_entries_015dc610cd81ede0: function(arg0) { + const ret = Object.entries(arg0); + return ret; + }, + __wbg_error_622f41c5c32a13db: function(arg0) { + const ret = arg0.error; + return ret; + }, + __wbg_error_657700d53a73881f: function(arg0, arg1, arg2, arg3) { + console.error(arg0, arg1, arg2, arg3); + }, + __wbg_error_744744ff0c9861e6: function(arg0) { + console.error(arg0); + }, + __wbg_error_7da16e6957d93dfc: function(arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); + } + }, + __wbg_features_2b07a28fe18ad0ce: function(arg0) { + const ret = arg0.features; + return ret; + }, + __wbg_fenceSync_fe2cdba4a0d73679: function(arg0, arg1, arg2) { + const ret = arg0.fenceSync(arg1 >>> 0, arg2 >>> 0); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_fetch_737feb0f4e4769cd: function(arg0, arg1) { + const ret = fetch(arg0, arg1); + return ret; + }, + __wbg_fetch_9dad4fe911207b37: function(arg0) { + const ret = fetch(arg0); + return ret; + }, + __wbg_fetch_b371b952a61cca04: function(arg0) { + const ret = fetch(arg0); + return ret; + }, + __wbg_fetch_b5951fc96f52f786: function(arg0, arg1) { + const ret = arg0.fetch(arg1); + return ret; + }, + __wbg_fetch_fadfd227089fdf80: function(arg0, arg1, arg2) { + const ret = arg0.fetch(arg1, arg2); + return ret; + }, + __wbg_files_116196bc012ac3c8: function(arg0) { + const ret = arg0.files; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_files_a4eb87e5e4343c46: function(arg0) { + const ret = arg0.files; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_finish_126e6f2ac71e3096: function(arg0) { + arg0.finish(); + }, + __wbg_finish_4d91de5e927dd13f: function(arg0, arg1) { + const ret = arg0.finish(arg1); + return ret; + }, + __wbg_finish_6e06b68ab68cd9f6: function(arg0) { + const ret = arg0.finish(); + return ret; + }, + __wbg_finish_cbe7ec8675dd7705: function(arg0) { + arg0.finish(); + }, + __wbg_flush_67fbd850838f4562: function(arg0) { + const ret = arg0.flush(); + return ret; + }, + __wbg_flush_db77b4a63d6b337d: function(arg0) { + arg0.flush(); + }, + __wbg_flush_e03c08da6863b5ab: function(arg0) { + arg0.flush(); + }, + __wbg_focus_5ecef5db03850e25: function() { return handleError(function (arg0, arg1) { + arg0.focus(arg1); + }, arguments); }, + __wbg_force_368c1897f399d783: function(arg0) { + const ret = arg0.force; + return ret; + }, + __wbg_forward_4bb54c7f45451c64: function() { return handleError(function (arg0) { + arg0.forward(); + }, arguments); }, + __wbg_framebufferRenderbuffer_4404cf9f9cb76937: function(arg0, arg1, arg2, arg3, arg4) { + arg0.framebufferRenderbuffer(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4); + }, + __wbg_framebufferRenderbuffer_ba8bd5e008ee87eb: function(arg0, arg1, arg2, arg3, arg4) { + arg0.framebufferRenderbuffer(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4); + }, + __wbg_framebufferTexture2D_3c2abd606fc53f31: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.framebufferTexture2D(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4, arg5); + }, + __wbg_framebufferTexture2D_e1fb64212fcda219: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.framebufferTexture2D(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4, arg5); + }, + __wbg_framebufferTextureLayer_f2d9db097bfbb863: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.framebufferTextureLayer(arg1 >>> 0, arg2 >>> 0, arg3, arg4, arg5); + }, + __wbg_framebufferTextureMultiviewOVR_28d492b9dc484220: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + arg0.framebufferTextureMultiviewOVR(arg1 >>> 0, arg2 >>> 0, arg3, arg4, arg5, arg6); + }, + __wbg_frontFace_29ef7151de8b5ed9: function(arg0, arg1) { + arg0.frontFace(arg1 >>> 0); + }, + __wbg_frontFace_fc6d98dafa42de87: function(arg0, arg1) { + arg0.frontFace(arg1 >>> 0); + }, + __wbg_getBindGroupLayout_8b86af56ae09c095: function(arg0, arg1) { + const ret = arg0.getBindGroupLayout(arg1 >>> 0); + return ret; + }, + __wbg_getBoundingClientRect_e828e6c31c66dea6: function(arg0) { + const ret = arg0.getBoundingClientRect(); + return ret; + }, + __wbg_getBufferSubData_11018928c908ac2c: function(arg0, arg1, arg2, arg3) { + arg0.getBufferSubData(arg1 >>> 0, arg2, arg3); + }, + __wbg_getComputedStyle_961681bdf7e518e8: function() { return handleError(function (arg0, arg1) { + const ret = arg0.getComputedStyle(arg1); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_getContext_7476e39fa008047e: function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = arg0.getContext(getStringFromWasm0(arg1, arg2), arg3); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_getContext_ca12bb65aab778a4: function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = arg0.getContext(getStringFromWasm0(arg1, arg2), arg3); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_getContext_e79ddf6a9cb3cc76: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.getContext(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_getContext_fd298c901058eb31: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.getContext(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_getCurrentTexture_20714d1bd9051cab: function() { return handleError(function (arg0) { + const ret = arg0.getCurrentTexture(); + return ret; + }, arguments); }, + __wbg_getData_fcb88fae21d94f1e: function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = arg1.getData(getStringFromWasm0(arg2, arg3)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_getDirectoryHandle_10036853d1aefa39: function(arg0, arg1, arg2) { + const ret = arg0.getDirectoryHandle(getStringFromWasm0(arg1, arg2)); + return ret; + }, + __wbg_getDirectoryHandle_cf175faf1a75a384: function(arg0, arg1, arg2, arg3) { + const ret = arg0.getDirectoryHandle(getStringFromWasm0(arg1, arg2), arg3); + return ret; + }, + __wbg_getDirectory_389283588dfb8117: function(arg0) { + const ret = arg0.getDirectory(); + return ret; + }, + __wbg_getElementById_1cbd8f06dbe8eb8e: function(arg0, arg1, arg2) { + const ret = arg0.getElementById(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_getExtension_101c7e41de3e4d90: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.getExtension(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_getFileHandle_72de55ab3ca9ad57: function(arg0, arg1, arg2, arg3) { + const ret = arg0.getFileHandle(getStringFromWasm0(arg1, arg2), arg3); + return ret; + }, + __wbg_getFileHandle_96903ab38e634823: function(arg0, arg1, arg2) { + const ret = arg0.getFileHandle(getStringFromWasm0(arg1, arg2)); + return ret; + }, + __wbg_getFile_bdc0144baa662031: function(arg0) { + const ret = arg0.getFile(); + return ret; + }, + __wbg_getIndexedParameter_6d7a5bcccaa0f3e2: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.getIndexedParameter(arg1 >>> 0, arg2 >>> 0); + return ret; + }, arguments); }, + __wbg_getItem_b96269ddc16cf24a: function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = arg1.getItem(getStringFromWasm0(arg2, arg3)); + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_getMappedRange_d0bf3141224111b6: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.getMappedRange(arg1, arg2); + return ret; + }, arguments); }, + __wbg_getParameter_039a5899307fab55: function() { return handleError(function (arg0, arg1) { + const ret = arg0.getParameter(arg1 >>> 0); + return ret; + }, arguments); }, + __wbg_getParameter_d39f59581389af1b: function() { return handleError(function (arg0, arg1) { + const ret = arg0.getParameter(arg1 >>> 0); + return ret; + }, arguments); }, + __wbg_getPreferredCanvasFormat_8b57039d1801a506: function(arg0) { + const ret = arg0.getPreferredCanvasFormat(); + return (__wbindgen_enum_GpuTextureFormat.indexOf(ret) + 1 || 102) - 1; + }, + __wbg_getProgramInfoLog_c4762e0513468a26: function(arg0, arg1, arg2) { + const ret = arg1.getProgramInfoLog(arg2); + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_getProgramInfoLog_d1ce570463a68779: function(arg0, arg1, arg2) { + const ret = arg1.getProgramInfoLog(arg2); + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_getProgramParameter_b9995b56c258ac86: function(arg0, arg1, arg2) { + const ret = arg0.getProgramParameter(arg1, arg2 >>> 0); + return ret; + }, + __wbg_getProgramParameter_c8d1154fbb3c0890: function(arg0, arg1, arg2) { + const ret = arg0.getProgramParameter(arg1, arg2 >>> 0); + return ret; + }, + __wbg_getPropertyValue_dc6b061239dad6f1: function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = arg1.getPropertyValue(getStringFromWasm0(arg2, arg3)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_getQueryParameter_919125495ccb17ca: function(arg0, arg1, arg2) { + const ret = arg0.getQueryParameter(arg1, arg2 >>> 0); + return ret; + }, + __wbg_getRandomValues_3f44b700395062e5: function() { return handleError(function (arg0, arg1) { + globalThis.crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1)); + }, arguments); }, + __wbg_getRandomValues_ceb34d8ffce7e87f: function() { return handleError(function (arg0, arg1) { + globalThis.crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1)); + }, arguments); }, + __wbg_getReader_b4b1868fbca77dbe: function() { return handleError(function (arg0) { + const ret = arg0.getReader(); + return ret; + }, arguments); }, + __wbg_getRootNode_f724c3be671a9a57: function(arg0) { + const ret = arg0.getRootNode(); + return ret; + }, + __wbg_getShaderInfoLog_5cee2add982c7165: function(arg0, arg1, arg2) { + const ret = arg1.getShaderInfoLog(arg2); + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_getShaderInfoLog_bc236afe696c1283: function(arg0, arg1, arg2) { + const ret = arg1.getShaderInfoLog(arg2); + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_getShaderParameter_3394e75dcb97f380: function(arg0, arg1, arg2) { + const ret = arg0.getShaderParameter(arg1, arg2 >>> 0); + return ret; + }, + __wbg_getShaderParameter_cbcc0995e8e16214: function(arg0, arg1, arg2) { + const ret = arg0.getShaderParameter(arg1, arg2 >>> 0); + return ret; + }, + __wbg_getSupportedExtensions_2a7458ec45e82560: function(arg0) { + const ret = arg0.getSupportedExtensions(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_getSupportedProfiles_90a4f330938d0241: function(arg0) { + const ret = arg0.getSupportedProfiles(); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_getSyncParameter_d8f6c145657a3550: function(arg0, arg1, arg2) { + const ret = arg0.getSyncParameter(arg1, arg2 >>> 0); + return ret; + }, + __wbg_getTime_d6f070c088c9b5ed: function(arg0) { + const ret = arg0.getTime(); + return ret; + }, + __wbg_getUniformBlockIndex_cfee6ff6d323c784: function(arg0, arg1, arg2, arg3) { + const ret = arg0.getUniformBlockIndex(arg1, getStringFromWasm0(arg2, arg3)); + return ret; + }, + __wbg_getUniformLocation_24ef46cdda2148ab: function(arg0, arg1, arg2, arg3) { + const ret = arg0.getUniformLocation(arg1, getStringFromWasm0(arg2, arg3)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_getUniformLocation_788a34295dd6fabe: function(arg0, arg1, arg2, arg3) { + const ret = arg0.getUniformLocation(arg1, getStringFromWasm0(arg2, arg3)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_get_507a50627bffa49b: function(arg0, arg1) { + const ret = arg0[arg1 >>> 0]; + return ret; + }, + __wbg_get_757c867e2520bbc4: function(arg0, arg1) { + const ret = arg0[arg1 >>> 0]; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_get_78f252d074a84d0b: function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(arg0, arg1); + return ret; + }, arguments); }, + __wbg_get_b2053e9bfdf3ca8e: function(arg0, arg1) { + const ret = arg0[arg1 >>> 0]; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_get_c7eb1f358a7654df: function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(arg0, arg1); + return ret; + }, arguments); }, + __wbg_get_ddcbbb3501c3011e: function(arg0, arg1) { + const ret = arg0[arg1 >>> 0]; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_get_done_670108eb06ecbe46: function(arg0) { + const ret = arg0.done; + return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0; + }, + __wbg_get_e73985d6689d2245: function(arg0, arg1) { + const ret = arg0[arg1 >>> 0]; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_get_unchecked_6e0ad6d2a41b06f6: function(arg0, arg1) { + const ret = arg0[arg1 >>> 0]; + return ret; + }, + __wbg_get_value_f465f5be30aa0963: function(arg0) { + const ret = arg0.value; + return ret; + }, + __wbg_get_with_ref_key_6412cf3094599694: function(arg0, arg1) { + const ret = arg0[arg1]; + return ret; + }, + __wbg_gpu_2ccc250735d24a2a: function(arg0) { + const ret = arg0.gpu; + return ret; + }, + __wbg_hasOwn_f2591afb8975e2fd: function(arg0, arg1) { + const ret = Object.hasOwn(arg0, arg1); + return ret; + }, + __wbg_has_0c97053e877f47cc: function(arg0, arg1, arg2) { + const ret = arg0.has(getStringFromWasm0(arg1, arg2)); + return ret; + }, + __wbg_has_8374cf06984d8bfc: function() { return handleError(function (arg0, arg1) { + const ret = Reflect.has(arg0, arg1); + return ret; + }, arguments); }, + __wbg_hash_508149c4291ec8c2: function() { return handleError(function (arg0, arg1) { + const ret = arg1.hash; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_headers_7b59c5203c8c475d: function(arg0) { + const ret = arg0.headers; + return ret; + }, + __wbg_headers_cf9c80f30e2a4eff: function(arg0) { + const ret = arg0.headers; + return ret; + }, + __wbg_height_1ac64d880e0a71ae: function(arg0) { + const ret = arg0.height; + return ret; + }, + __wbg_height_46f95580d0507f0a: function(arg0) { + const ret = arg0.height; + return ret; + }, + __wbg_height_5b881707f59cdee5: function(arg0) { + const ret = arg0.height; + return ret; + }, + __wbg_height_6eec812c213259a1: function(arg0) { + const ret = arg0.height; + return ret; + }, + __wbg_height_9f27216001e3c804: function(arg0) { + const ret = arg0.height; + return ret; + }, + __wbg_height_f2cc35b336f266f1: function(arg0) { + const ret = arg0.height; + return ret; + }, + __wbg_hidden_c08eb1c29c138ab0: function(arg0) { + const ret = arg0.hidden; + return ret; + }, + __wbg_history_e648b4314d9b256e: function() { return handleError(function (arg0) { + const ret = arg0.history; + return ret; + }, arguments); }, + __wbg_host_21c8d54c9bdcd04a: function() { return handleError(function (arg0, arg1) { + const ret = arg1.host; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_hostname_6a4adb791d7e7242: function() { return handleError(function (arg0, arg1) { + const ret = arg1.hostname; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_href_0259f7f614252f13: function() { return handleError(function (arg0, arg1) { + const ret = arg1.href; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_href_bc4909da4ed58381: function(arg0, arg1) { + const ret = arg1.href; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_identifier_d30bb260fab6b02a: function(arg0) { + const ret = arg0.identifier; + return ret; + }, + __wbg_includes_78c9a3115b08eddc: function(arg0, arg1, arg2) { + const ret = arg0.includes(arg1, arg2); + return ret; + }, + __wbg_info_79f5309d69d9c70e: function(arg0, arg1, arg2, arg3) { + console.info(arg0, arg1, arg2, arg3); + }, + __wbg_info_cf0d9a286850cd24: function(arg0) { + const ret = arg0.info; + return ret; + }, + __wbg_info_eadbe775a8e2e9eb: function(arg0) { + console.info(arg0); + }, + __wbg_inlineSize_3c8412828bef21eb: function(arg0) { + const ret = arg0.inlineSize; + return ret; + }, + __wbg_inputType_37c59110203135d8: function(arg0, arg1) { + const ret = arg1.inputType; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_insertBefore_9121f73148bc4f7c: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.insertBefore(arg1, arg2); + return ret; + }, arguments); }, + __wbg_instanceof_ArrayBuffer_4480b9e0068a8adb: function(arg0) { + let result; + try { + result = arg0 instanceof ArrayBuffer; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_Document_d1955f84f5d0351c: function(arg0) { + let result; + try { + result = arg0 instanceof Document; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_DomException_952faa8037702c00: function(arg0) { + let result; + try { + result = arg0 instanceof DOMException; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_Element_beebfaab75d12d9d: function(arg0) { + let result; + try { + result = arg0 instanceof Element; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_Error_1fdac9f13a8181ba: function(arg0) { + let result; + try { + result = arg0 instanceof Error; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_FileSystemDirectoryHandle_c9ab7c5cdb7a7c30: function(arg0) { + let result; + try { + result = arg0 instanceof FileSystemDirectoryHandle; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_FileSystemFileHandle_68e80b30532d5f04: function(arg0) { + let result; + try { + result = arg0 instanceof FileSystemFileHandle; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_FileSystemWritableFileStream_bbd33ec1789b2714: function(arg0) { + let result; + try { + result = arg0 instanceof FileSystemWritableFileStream; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_File_ee62de53bca2e697: function(arg0) { + let result; + try { + result = arg0 instanceof File; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_GpuOutOfMemoryError_6429c750997f1c8d: function(arg0) { + let result; + try { + result = arg0 instanceof GPUOutOfMemoryError; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_GpuValidationError_75fa3611f065f4df: function(arg0) { + let result; + try { + result = arg0 instanceof GPUValidationError; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_HtmlAnchorElement_0b37fbaa9075f12c: function(arg0) { + let result; + try { + result = arg0 instanceof HTMLAnchorElement; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_HtmlButtonElement_2798f3f046c1c446: function(arg0) { + let result; + try { + result = arg0 instanceof HTMLButtonElement; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_HtmlCanvasElement_ed02ed9136056019: function(arg0) { + let result; + try { + result = arg0 instanceof HTMLCanvasElement; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_HtmlElement_4493a09212d3586f: function(arg0) { + let result; + try { + result = arg0 instanceof HTMLElement; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_HtmlInputElement_ad3be04339d0e4df: function(arg0) { + let result; + try { + result = arg0 instanceof HTMLInputElement; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_ImageBitmap_859f193922076b2b: function(arg0) { + let result; + try { + result = arg0 instanceof ImageBitmap; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_MessageEvent_7d226bddd45cb41d: function(arg0) { + let result; + try { + result = arg0 instanceof MessageEvent; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_Promise_4cb210c0b8f8c959: function(arg0) { + let result; + try { + result = arg0 instanceof Promise; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_ReadableStream_3bdc7d10b03fd402: function(arg0) { + let result; + try { + result = arg0 instanceof ReadableStream; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_ResizeObserverEntry_5d9f44b2d0d4bd47: function(arg0) { + let result; + try { + result = arg0 instanceof ResizeObserverEntry; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_ResizeObserverSize_52b48dee6a4ab521: function(arg0) { + let result; + try { + result = arg0 instanceof ResizeObserverSize; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_Response_c8b64b2256f01bec: function(arg0) { + let result; + try { + result = arg0 instanceof Response; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_ShadowRoot_8ab3038bc5e14d84: function(arg0) { + let result; + try { + result = arg0 instanceof ShadowRoot; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_TypeError_aa4c0c0517d53151: function(arg0) { + let result; + try { + result = arg0 instanceof TypeError; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_Uint8Array_309b927aaf7a3fc7: function(arg0) { + let result; + try { + result = arg0 instanceof Uint8Array; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_WebGl2RenderingContext_90225152e4e3c799: function(arg0) { + let result; + try { + result = arg0 instanceof WebGL2RenderingContext; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_instanceof_Window_05ba1ee4f6781663: function(arg0) { + let result; + try { + result = arg0 instanceof Window; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }, + __wbg_invalidateFramebuffer_343bbfb15e6835fd: function() { return handleError(function (arg0, arg1, arg2) { + arg0.invalidateFramebuffer(arg1 >>> 0, arg2); + }, arguments); }, + __wbg_isActive_9e21f95da89fce93: function(arg0) { + const ret = arg0.isActive; + return ret; + }, + __wbg_isArray_0677c962b281d01a: function(arg0) { + const ret = Array.isArray(arg0); + return ret; + }, + __wbg_isComposing_3022d9ff79b517bd: function(arg0) { + const ret = arg0.isComposing; + return ret; + }, + __wbg_isComposing_919a0fdf6ac030c9: function(arg0) { + const ret = arg0.isComposing; + return ret; + }, + __wbg_isFallbackAdapter_8ccb967428491dcb: function(arg0) { + const ret = arg0.isFallbackAdapter; + return ret; + }, + __wbg_isSafeInteger_04f36e4056f1b851: function(arg0) { + const ret = Number.isSafeInteger(arg0); + return ret; + }, + __wbg_isSecureContext_d2e906a088ea2127: function(arg0) { + const ret = arg0.isSecureContext; + return ret; + }, + __wbg_is_7b9d0b289033c7de: function(arg0, arg1) { + const ret = Object.is(arg0, arg1); + return ret; + }, + __wbg_item_4b2887fd8cb17be5: function(arg0, arg1) { + const ret = arg0.item(arg1 >>> 0); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_items_350b6f2d566d3def: function(arg0) { + const ret = arg0.items; + return ret; + }, + __wbg_iterator_6f722e4a93058b71: function() { + const ret = Symbol.iterator; + return ret; + }, + __wbg_keyCode_f9ab89c2dd6c3770: function(arg0) { + const ret = arg0.keyCode; + return ret; + }, + __wbg_key_803dca86cdcfa8dd: function(arg0, arg1) { + const ret = arg1.key; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_key_c3e1e6137d321e2c: function(arg0, arg1) { + const ret = arg1.key; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_label_7ed42f25f841996b: function(arg0, arg1) { + const ret = arg1.label; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_left_7e76a74d0db1754f: function(arg0) { + const ret = arg0.left; + return ret; + }, + __wbg_length_1f0964f4a5e2c6d8: function(arg0) { + const ret = arg0.length; + return ret; + }, + __wbg_length_370319915dc99107: function(arg0) { + const ret = arg0.length; + return ret; + }, + __wbg_length_e08fc23135c66d6f: function(arg0) { + const ret = arg0.length; + return ret; + }, + __wbg_length_eea4bfa35e75c87c: function(arg0) { + const ret = arg0.length; + return ret; + }, + __wbg_length_ef21514bf74fe712: function(arg0) { + const ret = arg0.length; + return ret; + }, + __wbg_limits_20c6f56636df7d38: function(arg0) { + const ret = arg0.limits; + return ret; + }, + __wbg_limits_328c61cd41512420: function(arg0) { + const ret = arg0.limits; + return ret; + }, + __wbg_linkProgram_4e047fb3197a0348: function(arg0, arg1) { + arg0.linkProgram(arg1); + }, + __wbg_linkProgram_d7c71c539c8c6a43: function(arg0, arg1) { + arg0.linkProgram(arg1); + }, + __wbg_localStorage_5bf6ce3f8e51412a: function() { return handleError(function (arg0) { + const ret = arg0.localStorage; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_location_c9a2271428996698: function(arg0) { + const ret = arg0.location; + return ret; + }, + __wbg_mapAsync_52b01fa9e8f765fd: function(arg0, arg1, arg2, arg3) { + const ret = arg0.mapAsync(arg1 >>> 0, arg2, arg3); + return ret; + }, + __wbg_matchMedia_9968278b31706f78: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.matchMedia(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_matches_978994974df1e85b: function(arg0) { + const ret = arg0.matches; + return ret; + }, + __wbg_maxBindGroupsPlusVertexBuffers_33e5006b23e20478: function(arg0) { + const ret = arg0.maxBindGroupsPlusVertexBuffers; + return ret; + }, + __wbg_maxBindGroups_f6d26f3a67826666: function(arg0) { + const ret = arg0.maxBindGroups; + return ret; + }, + __wbg_maxBindingsPerBindGroup_edab2e8dabbf6060: function(arg0) { + const ret = arg0.maxBindingsPerBindGroup; + return ret; + }, + __wbg_maxBufferSize_bbc69284c14aa7da: function(arg0) { + const ret = arg0.maxBufferSize; + return ret; + }, + __wbg_maxColorAttachmentBytesPerSample_63ebe4f81de2f34c: function(arg0) { + const ret = arg0.maxColorAttachmentBytesPerSample; + return ret; + }, + __wbg_maxColorAttachments_aed8c38beabf3a5c: function(arg0) { + const ret = arg0.maxColorAttachments; + return ret; + }, + __wbg_maxComputeInvocationsPerWorkgroup_2d964564c37f1c65: function(arg0) { + const ret = arg0.maxComputeInvocationsPerWorkgroup; + return ret; + }, + __wbg_maxComputeWorkgroupSizeX_a3e3206570da184f: function(arg0) { + const ret = arg0.maxComputeWorkgroupSizeX; + return ret; + }, + __wbg_maxComputeWorkgroupSizeY_dffa4a62244b7563: function(arg0) { + const ret = arg0.maxComputeWorkgroupSizeY; + return ret; + }, + __wbg_maxComputeWorkgroupSizeZ_976ebcb760f6d07d: function(arg0) { + const ret = arg0.maxComputeWorkgroupSizeZ; + return ret; + }, + __wbg_maxComputeWorkgroupStorageSize_2e8dbece6e532e2a: function(arg0) { + const ret = arg0.maxComputeWorkgroupStorageSize; + return ret; + }, + __wbg_maxComputeWorkgroupsPerDimension_bb7d36b4d20c80f4: function(arg0) { + const ret = arg0.maxComputeWorkgroupsPerDimension; + return ret; + }, + __wbg_maxDynamicStorageBuffersPerPipelineLayout_1ca859cb96a414e0: function(arg0) { + const ret = arg0.maxDynamicStorageBuffersPerPipelineLayout; + return ret; + }, + __wbg_maxDynamicUniformBuffersPerPipelineLayout_e968f2c8cd8f4d46: function(arg0) { + const ret = arg0.maxDynamicUniformBuffersPerPipelineLayout; + return ret; + }, + __wbg_maxInterStageShaderVariables_138ac882c4d6a3d3: function(arg0) { + const ret = arg0.maxInterStageShaderVariables; + return ret; + }, + __wbg_maxSampledTexturesPerShaderStage_bb3e6b2698321fa6: function(arg0) { + const ret = arg0.maxSampledTexturesPerShaderStage; + return ret; + }, + __wbg_maxSamplersPerShaderStage_98c00a1829fa414b: function(arg0) { + const ret = arg0.maxSamplersPerShaderStage; + return ret; + }, + __wbg_maxStorageBufferBindingSize_e500e31f479e669e: function(arg0) { + const ret = arg0.maxStorageBufferBindingSize; + return ret; + }, + __wbg_maxStorageBuffersPerShaderStage_eb663f6d7521b6a7: function(arg0) { + const ret = arg0.maxStorageBuffersPerShaderStage; + return ret; + }, + __wbg_maxStorageTexturesPerShaderStage_bb3ad93b53e618c0: function(arg0) { + const ret = arg0.maxStorageTexturesPerShaderStage; + return ret; + }, + __wbg_maxTextureArrayLayers_2a56d05fb261c99a: function(arg0) { + const ret = arg0.maxTextureArrayLayers; + return ret; + }, + __wbg_maxTextureDimension1D_84590c1d4770d319: function(arg0) { + const ret = arg0.maxTextureDimension1D; + return ret; + }, + __wbg_maxTextureDimension2D_7f2b5c8b2727e3fc: function(arg0) { + const ret = arg0.maxTextureDimension2D; + return ret; + }, + __wbg_maxTextureDimension3D_7f3babddf55c32a6: function(arg0) { + const ret = arg0.maxTextureDimension3D; + return ret; + }, + __wbg_maxUniformBufferBindingSize_d80a09e23c0b284c: function(arg0) { + const ret = arg0.maxUniformBufferBindingSize; + return ret; + }, + __wbg_maxUniformBuffersPerShaderStage_0b8b2de676fa740e: function(arg0) { + const ret = arg0.maxUniformBuffersPerShaderStage; + return ret; + }, + __wbg_maxVertexAttributes_a693dd921316649b: function(arg0) { + const ret = arg0.maxVertexAttributes; + return ret; + }, + __wbg_maxVertexBufferArrayStride_f256d91f281076cb: function(arg0) { + const ret = arg0.maxVertexBufferArrayStride; + return ret; + }, + __wbg_maxVertexBuffers_70ab564b25d5ac20: function(arg0) { + const ret = arg0.maxVertexBuffers; + return ret; + }, + __wbg_message_4ada57a3710f1502: function(arg0, arg1) { + const ret = arg1.message; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_message_fb0e6e7854e6ea7a: function(arg0, arg1) { + const ret = arg1.message; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_metaKey_d961c7572a9f84f5: function(arg0) { + const ret = arg0.metaKey; + return ret; + }, + __wbg_metaKey_f934f09e37889d70: function(arg0) { + const ret = arg0.metaKey; + return ret; + }, + __wbg_minStorageBufferOffsetAlignment_3248ed00dcdbf79f: function(arg0) { + const ret = arg0.minStorageBufferOffsetAlignment; + return ret; + }, + __wbg_minUniformBufferOffsetAlignment_3b9fa4caae03e903: function(arg0) { + const ret = arg0.minUniformBufferOffsetAlignment; + return ret; + }, + __wbg_name_9d2bcd24d4433cef: function(arg0, arg1) { + const ret = arg1.name; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_name_d7d79f5466e37447: function(arg0, arg1) { + const ret = arg1.name; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_navigator_51379c10a84aeec9: function(arg0) { + const ret = arg0.navigator; + return ret; + }, + __wbg_navigator_99621db14b3f1099: function(arg0) { + const ret = arg0.navigator; + return ret; + }, + __wbg_newValue_6d3d665995a61f5e: function(arg0, arg1) { + const ret = arg1.newValue; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_new_08cb2fa678b17a48: function() { return handleError(function (arg0, arg1) { + const ret = new URL(getStringFromWasm0(arg0, arg1)); + return ret; + }, arguments); }, + __wbg_new_0_3da9e97f24fc69be: function() { + const ret = new Date(); + return ret; + }, + __wbg_new_0d809930cd1354c6: function() { return handleError(function () { + const ret = new Headers(); + return ret; + }, arguments); }, + __wbg_new_108a91a98f879343: function() { + const ret = new Error(); + return ret; + }, + __wbg_new_25e75d1f0df4d87a: function() { return handleError(function (arg0, arg1) { + const ret = new OffscreenCanvas(arg0 >>> 0, arg1 >>> 0); + return ret; + }, arguments); }, + __wbg_new_32b398fb48b6d94a: function() { + const ret = new Array(); + return ret; + }, + __wbg_new_4339b2a2675a03e3: function() { return handleError(function () { + const ret = new AbortController(); + return ret; + }, arguments); }, + __wbg_new_51c73e5617fb3fad: function() { return handleError(function (arg0) { + const ret = new EncodedVideoChunk(arg0); + return ret; + }, arguments); }, + __wbg_new_5394f65338077341: function() { return handleError(function (arg0) { + const ret = new ResizeObserver(arg0); + return ret; + }, arguments); }, + __wbg_new_5e245ef5857d7f33: function(arg0, arg1) { + const ret = new TypeError(getStringFromWasm0(arg0, arg1)); + return ret; + }, + __wbg_new_a211ccc53aa68944: function() { return handleError(function (arg0) { + const ret = new VideoDecoder(arg0); + return ret; + }, arguments); }, + __wbg_new_aec3e25493d729fe: function(arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return wasm_bindgen__convert__closures_____invoke__h4a090e5af75dc439(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return ret; + } finally { + state0.a = 0; + } + }, + __wbg_new_b667d279fd5aa943: function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return ret; + }, + __wbg_new_c9eb879b62d87c93: function() { + const ret = new Error(); + return ret; + }, + __wbg_new_cbb95886ce0eb0cb: function(arg0, arg1) { + const ret = new Intl.DateTimeFormat(arg0, arg1); + return ret; + }, + __wbg_new_cd45aabdf6073e84: function(arg0) { + const ret = new Uint8Array(arg0); + return ret; + }, + __wbg_new_da52cf8fe3429cb2: function() { + const ret = new Object(); + return ret; + }, + __wbg_new_f0787df90791d9ba: function() { return handleError(function () { + const ret = new URLSearchParams(); + return ret; + }, arguments); }, + __wbg_new_from_slice_77cdfb7977362f3c: function(arg0, arg1) { + const ret = new Uint8Array(getArrayU8FromWasm0(arg0, arg1)); + return ret; + }, + __wbg_new_typed_1824d93f294193e5: function(arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return wasm_bindgen__convert__closures_____invoke__h4a090e5af75dc439(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return ret; + } finally { + state0.a = 0; + } + }, + __wbg_new_typed_4148bd5ae72ab3f0: function() { + const ret = new Object(); + return ret; + }, + __wbg_new_with_byte_offset_and_length_54c7724ee3ec7d82: function(arg0, arg1, arg2) { + const ret = new Uint8Array(arg0, arg1 >>> 0, arg2 >>> 0); + return ret; + }, + __wbg_new_with_image_bitmap_and_video_frame_init_0db8a370be656c2c: function() { return handleError(function (arg0, arg1) { + const ret = new VideoFrame(arg0, arg1); + return ret; + }, arguments); }, + __wbg_new_with_length_e6785c33c8e4cce8: function(arg0) { + const ret = new Uint8Array(arg0 >>> 0); + return ret; + }, + __wbg_new_with_record_from_str_to_blob_promise_6112280bc8a0f052: function() { return handleError(function (arg0) { + const ret = new ClipboardItem(arg0); + return ret; + }, arguments); }, + __wbg_new_with_str_and_init_d95cbe11ce28e65e: function() { return handleError(function (arg0, arg1, arg2) { + const ret = new Request(getStringFromWasm0(arg0, arg1), arg2); + return ret; + }, arguments); }, + __wbg_new_with_u8_array_sequence_and_options_2c1900e5a5c93850: function() { return handleError(function (arg0, arg1) { + const ret = new Blob(arg0, arg1); + return ret; + }, arguments); }, + __wbg_nextSibling_0e94ccfa3c22fa3c: function(arg0) { + const ret = arg0.nextSibling; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_next_6dbf2c0ac8cde20f: function(arg0) { + const ret = arg0.next; + return ret; + }, + __wbg_next_71f2aa1cb3d1e37e: function() { return handleError(function (arg0) { + const ret = arg0.next(); + return ret; + }, arguments); }, + __wbg_now_390768da5ee9e776: function(arg0) { + const ret = arg0.now(); + return ret; + }, + __wbg_now_86c0d4ba3fa605b8: function() { + const ret = Date.now(); + return ret; + }, + __wbg_now_e7c6795a7f81e10f: function(arg0) { + const ret = arg0.now(); + return ret; + }, + __wbg_observe_615bef91ee28c925: function(arg0, arg1, arg2) { + arg0.observe(arg1, arg2); + }, + __wbg_of_85f52f8b6491a7ca: function(arg0) { + const ret = Array.of(arg0); + return ret; + }, + __wbg_offsetLeft_57573e1411874d68: function(arg0) { + const ret = arg0.offsetLeft; + return ret; + }, + __wbg_offsetTop_eb7a93213506ba96: function(arg0) { + const ret = arg0.offsetTop; + return ret; + }, + __wbg_ok_acc5e3fb89668864: function(arg0) { + const ret = arg0.ok; + return ret; + }, + __wbg_onSubmittedWorkDone_270d6b5a45520e79: function(arg0) { + const ret = arg0.onSubmittedWorkDone(); + return ret; + }, + __wbg_open_1a1b29d8bfde6885: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + const ret = arg0.open(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4), getStringFromWasm0(arg5, arg6)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_open_221b279749ba2e4e: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + const ret = arg0.open(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_origin_ed66c06e67ad2049: function() { return handleError(function (arg0, arg1) { + const ret = arg1.origin; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_parentNode_fecbbdea2a930547: function(arg0) { + const ret = arg0.parentNode; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_performance_3ef602e13d6c3b56: function(arg0) { + const ret = arg0.performance; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_performance_3fcf6e32a7e1ed0a: function(arg0) { + const ret = arg0.performance; + return ret; + }, + __wbg_persist_5ddcf223b47da9ba: function() { return handleError(function (arg0) { + const ret = arg0.persist(); + return ret; + }, arguments); }, + __wbg_pipeTo_3ecb20e17416edd6: function(arg0, arg1) { + const ret = arg0.pipeTo(arg1); + return ret; + }, + __wbg_pixelStorei_2a93b18efde9acf8: function(arg0, arg1, arg2) { + arg0.pixelStorei(arg1 >>> 0, arg2); + }, + __wbg_pixelStorei_c844cd0db4f1fde6: function(arg0, arg1, arg2) { + arg0.pixelStorei(arg1 >>> 0, arg2); + }, + __wbg_polygonOffset_4eb460adf41db6cd: function(arg0, arg1, arg2) { + arg0.polygonOffset(arg1, arg2); + }, + __wbg_polygonOffset_eccb68e40a18f861: function(arg0, arg1, arg2) { + arg0.polygonOffset(arg1, arg2); + }, + __wbg_port_e2c291cf8fd5fc40: function() { return handleError(function (arg0, arg1) { + const ret = arg1.port; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_preventDefault_b64888c857500682: function(arg0) { + arg0.preventDefault(); + }, + __wbg_protocol_0598aef25eb71eae: function() { return handleError(function (arg0, arg1) { + const ret = arg1.protocol; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_prototypesetcall_4770620bbe4688a0: function(arg0, arg1, arg2) { + Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2); + }, + __wbg_pushState_3d01701623122bc8: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.pushState(arg1, getStringFromWasm0(arg2, arg3), arg4 === 0 ? undefined : getStringFromWasm0(arg4, arg5)); + }, arguments); }, + __wbg_push_d2ae3af0c1217ae6: function(arg0, arg1) { + const ret = arg0.push(arg1); + return ret; + }, + __wbg_queryCounterEXT_b74a4567ddfeecf0: function(arg0, arg1, arg2) { + arg0.queryCounterEXT(arg1, arg2 >>> 0); + }, + __wbg_querySelectorAll_7e98cbe256deaadd: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.querySelectorAll(getStringFromWasm0(arg1, arg2)); + return ret; + }, arguments); }, + __wbg_querySelector_fd7d157ebe17cd16: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.querySelector(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments); }, + __wbg_queueMicrotask_0ab5b2d2393e99b9: function(arg0) { + const ret = arg0.queueMicrotask; + return ret; + }, + __wbg_queueMicrotask_6a09b7bc46549209: function(arg0) { + queueMicrotask(arg0); + }, + __wbg_queue_adce34608fd0c893: function(arg0) { + const ret = arg0.queue; + return ret; + }, + __wbg_readBuffer_4271437a70aae481: function(arg0, arg1) { + arg0.readBuffer(arg1 >>> 0); + }, + __wbg_readPixels_5f013a7d85b23800: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { + arg0.readPixels(arg1, arg2, arg3, arg4, arg5 >>> 0, arg6 >>> 0, arg7); + }, arguments); }, + __wbg_readPixels_82c9dee754d58176: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { + arg0.readPixels(arg1, arg2, arg3, arg4, arg5 >>> 0, arg6 >>> 0, arg7); + }, arguments); }, + __wbg_readPixels_c7861e25836bf57b: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { + arg0.readPixels(arg1, arg2, arg3, arg4, arg5 >>> 0, arg6 >>> 0, arg7); + }, arguments); }, + __wbg_read_8afa15f12a160ef8: function(arg0) { + const ret = arg0.read(); + return ret; + }, + __wbg_releaseLock_5b92874cad775644: function(arg0) { + arg0.releaseLock(); + }, + __wbg_removeChild_8d9536328d674d54: function() { return handleError(function (arg0, arg1) { + const ret = arg0.removeChild(arg1); + return ret; + }, arguments); }, + __wbg_removeEventListener_a3f23c70077bdcc1: function() { return handleError(function (arg0, arg1, arg2, arg3) { + arg0.removeEventListener(getStringFromWasm0(arg1, arg2), arg3); + }, arguments); }, + __wbg_removeItem_78e03a38da96e0ae: function() { return handleError(function (arg0, arg1, arg2) { + arg0.removeItem(getStringFromWasm0(arg1, arg2)); + }, arguments); }, + __wbg_remove_ce1b54059317fe8a: function(arg0) { + arg0.remove(); + }, + __wbg_renderbufferStorageMultisample_5c6e5d20c0eaa6ba: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.renderbufferStorageMultisample(arg1 >>> 0, arg2, arg3 >>> 0, arg4, arg5); + }, + __wbg_renderbufferStorage_0a8de92542893819: function(arg0, arg1, arg2, arg3, arg4) { + arg0.renderbufferStorage(arg1 >>> 0, arg2 >>> 0, arg3, arg4); + }, + __wbg_renderbufferStorage_ab5f745ff8efce3d: function(arg0, arg1, arg2, arg3, arg4) { + arg0.renderbufferStorage(arg1 >>> 0, arg2 >>> 0, arg3, arg4); + }, + __wbg_replaceState_9a0a4a53d3bf3439: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.replaceState(arg1, getStringFromWasm0(arg2, arg3), arg4 === 0 ? undefined : getStringFromWasm0(arg4, arg5)); + }, arguments); }, + __wbg_requestAdapter_2e6718811c735a57: function(arg0, arg1) { + const ret = arg0.requestAdapter(arg1); + return ret; + }, + __wbg_requestAdapter_fedd76261c649e55: function(arg0) { + const ret = arg0.requestAdapter(); + return ret; + }, + __wbg_requestAnimationFrame_1a85deeab66448c2: function() { return handleError(function (arg0, arg1) { + const ret = arg0.requestAnimationFrame(arg1); + return ret; + }, arguments); }, + __wbg_requestDevice_ab46d0519ea1cc34: function(arg0, arg1) { + const ret = arg0.requestDevice(arg1); + return ret; + }, + __wbg_reset_5f89fa780634d36d: function() { return handleError(function (arg0) { + arg0.reset(); + }, arguments); }, + __wbg_resolve_2191a4dfe481c25b: function(arg0) { + const ret = Promise.resolve(arg0); + return ret; + }, + __wbg_resolvedOptions_ce0ede387898d6bd: function(arg0) { + const ret = arg0.resolvedOptions(); + return ret; + }, + __wbg_respond_510e32df8aeb6817: function() { return handleError(function (arg0, arg1) { + arg0.respond(arg1 >>> 0); + }, arguments); }, + __wbg_right_36c53e00496f4f0a: function(arg0) { + const ret = arg0.right; + return ret; + }, + __wbg_samplerParameterf_0b3308eeb1faa3a1: function(arg0, arg1, arg2, arg3) { + arg0.samplerParameterf(arg1, arg2 >>> 0, arg3); + }, + __wbg_samplerParameteri_7b1b4091de49aabb: function(arg0, arg1, arg2, arg3) { + arg0.samplerParameteri(arg1, arg2 >>> 0, arg3); + }, + __wbg_scissor_105e756596bc35df: function(arg0, arg1, arg2, arg3, arg4) { + arg0.scissor(arg1, arg2, arg3, arg4); + }, + __wbg_scissor_573b844152316b8d: function(arg0, arg1, arg2, arg3, arg4) { + arg0.scissor(arg1, arg2, arg3, arg4); + }, + __wbg_searchParams_23a839468a61ef4c: function(arg0) { + const ret = arg0.searchParams; + return ret; + }, + __wbg_search_af2555aa41bd23cc: function() { return handleError(function (arg0, arg1) { + const ret = arg1.search; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_selectionEnd_bc4cb81b30d7175a: function() { return handleError(function (arg0) { + const ret = arg0.selectionEnd; + return isLikeNone(ret) ? Number.MAX_SAFE_INTEGER : (ret) >>> 0; + }, arguments); }, + __wbg_selectionStart_965d703fd02aafa2: function() { return handleError(function (arg0) { + const ret = arg0.selectionStart; + return isLikeNone(ret) ? Number.MAX_SAFE_INTEGER : (ret) >>> 0; + }, arguments); }, + __wbg_setAttribute_71039043be82d098: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + arg0.setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, arguments); }, + __wbg_setBindGroup_268fd1714fff0ef5: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + arg0.setBindGroup(arg1 >>> 0, arg2, getArrayU32FromWasm0(arg3, arg4), arg5, arg6 >>> 0); + }, arguments); }, + __wbg_setBindGroup_f0de6cb2c7dbfc2c: function(arg0, arg1, arg2) { + arg0.setBindGroup(arg1 >>> 0, arg2); + }, + __wbg_setIndexBuffer_2531a9103450445e: function(arg0, arg1, arg2, arg3) { + arg0.setIndexBuffer(arg1, __wbindgen_enum_GpuIndexFormat[arg2], arg3); + }, + __wbg_setIndexBuffer_7f3cf667b4d71566: function(arg0, arg1, arg2, arg3, arg4) { + arg0.setIndexBuffer(arg1, __wbindgen_enum_GpuIndexFormat[arg2], arg3, arg4); + }, + __wbg_setItem_364a11cf21db9039: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + arg0.setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, arguments); }, + __wbg_setPipeline_c41bf46790f27f9e: function(arg0, arg1) { + arg0.setPipeline(arg1); + }, + __wbg_setProperty_e4e51b1b1d681d15: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + arg0.setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, arguments); }, + __wbg_setScissorRect_42511fefb18b86ef: function(arg0, arg1, arg2, arg3, arg4) { + arg0.setScissorRect(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4 >>> 0); + }, + __wbg_setTimeout_91734b3436ad0e03: function(arg0, arg1) { + const ret = setTimeout(arg0, arg1); + return ret; + }, + __wbg_setTimeout_cfa2cf195c3738db: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.setTimeout(arg1, arg2); + return ret; + }, arguments); }, + __wbg_setTimeout_f757f00851f76c42: function(arg0, arg1) { + const ret = setTimeout(arg0, arg1); + return ret; + }, + __wbg_setVertexBuffer_1e448859663dd400: function(arg0, arg1, arg2, arg3) { + arg0.setVertexBuffer(arg1 >>> 0, arg2, arg3); + }, + __wbg_setVertexBuffer_7cf533d694e747f3: function(arg0, arg1, arg2, arg3, arg4) { + arg0.setVertexBuffer(arg1 >>> 0, arg2, arg3, arg4); + }, + __wbg_setViewport_d9fc3eac343de7d0: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + arg0.setViewport(arg1, arg2, arg3, arg4, arg5, arg6); + }, + __wbg_set_0de9c62c23d04ad5: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + arg0.set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, arguments); }, + __wbg_set_4d7dd76f3dae2926: function(arg0, arg1, arg2) { + arg0.set(getArrayU8FromWasm0(arg1, arg2)); + }, + __wbg_set_61e45ae8061eca11: function(arg0, arg1, arg2) { + arg0.set(arg1, arg2 >>> 0); + }, + __wbg_set_6be42768c690e380: function(arg0, arg1, arg2) { + arg0[arg1] = arg2; + }, + __wbg_set_8535240470bf2500: function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(arg0, arg1, arg2); + return ret; + }, arguments); }, + __wbg_set_a_88262a42340d0b1c: function(arg0, arg1) { + arg0.a = arg1; + }, + __wbg_set_accept_5906cb0d4eb6ea4d: function(arg0, arg1, arg2) { + arg0.accept = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_access_9a5092f05dc45fad: function(arg0, arg1) { + arg0.access = __wbindgen_enum_GpuStorageTextureAccess[arg1]; + }, + __wbg_set_address_mode_u_9e2695575a219e33: function(arg0, arg1) { + arg0.addressModeU = __wbindgen_enum_GpuAddressMode[arg1]; + }, + __wbg_set_address_mode_v_f479b2e6cccbcac4: function(arg0, arg1) { + arg0.addressModeV = __wbindgen_enum_GpuAddressMode[arg1]; + }, + __wbg_set_address_mode_w_46273e153230180d: function(arg0, arg1) { + arg0.addressModeW = __wbindgen_enum_GpuAddressMode[arg1]; + }, + __wbg_set_alpha_bfd2df62e7bc581b: function(arg0, arg1) { + arg0.alpha = arg1; + }, + __wbg_set_alpha_mode_df805952892caa9c: function(arg0, arg1) { + arg0.alphaMode = __wbindgen_enum_GpuCanvasAlphaMode[arg1]; + }, + __wbg_set_alpha_to_coverage_enabled_8b5dc2b0a225b3b2: function(arg0, arg1) { + arg0.alphaToCoverageEnabled = arg1 !== 0; + }, + __wbg_set_array_layer_count_7312f0f31af94e7c: function(arg0, arg1) { + arg0.arrayLayerCount = arg1 >>> 0; + }, + __wbg_set_array_stride_f64_27ffaf4fffd74e61: function(arg0, arg1) { + arg0.arrayStride = arg1; + }, + __wbg_set_aspect_0d453bca3d012f02: function(arg0, arg1) { + arg0.aspect = __wbindgen_enum_GpuTextureAspect[arg1]; + }, + __wbg_set_aspect_210da747c9d77aba: function(arg0, arg1) { + arg0.aspect = __wbindgen_enum_GpuTextureAspect[arg1]; + }, + __wbg_set_aspect_4962514fe99e68e6: function(arg0, arg1) { + arg0.aspect = __wbindgen_enum_GpuTextureAspect[arg1]; + }, + __wbg_set_attributes_7537844a7e6dafdc: function(arg0, arg1, arg2) { + arg0.attributes = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_b_c47befe0af3261eb: function(arg0, arg1) { + arg0.b = arg1; + }, + __wbg_set_base_array_layer_f176bb9f1b37b342: function(arg0, arg1) { + arg0.baseArrayLayer = arg1 >>> 0; + }, + __wbg_set_base_mip_level_1df145d9f8db32a9: function(arg0, arg1) { + arg0.baseMipLevel = arg1 >>> 0; + }, + __wbg_set_bbfffe9b60e58c38: function(arg0, arg1, arg2, arg3, arg4) { + arg0.set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }, + __wbg_set_beginning_of_pass_write_index_e9f5d016947893bd: function(arg0, arg1) { + arg0.beginningOfPassWriteIndex = arg1 >>> 0; + }, + __wbg_set_bind_group_layouts_5a9cfea401c020ab: function(arg0, arg1, arg2) { + arg0.bindGroupLayouts = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_binding_155b0440b4307793: function(arg0, arg1) { + arg0.binding = arg1 >>> 0; + }, + __wbg_set_binding_f74df3510792aba1: function(arg0, arg1) { + arg0.binding = arg1 >>> 0; + }, + __wbg_set_blend_7493c2066c3e9970: function(arg0, arg1) { + arg0.blend = arg1; + }, + __wbg_set_body_029f2d171e0a005f: function(arg0, arg1) { + arg0.body = arg1; + }, + __wbg_set_box_223b9bc0b7f548f6: function(arg0, arg1) { + arg0.box = __wbindgen_enum_ResizeObserverBoxOptions[arg1]; + }, + __wbg_set_buffer_9c01e3b6d6765ea2: function(arg0, arg1) { + arg0.buffer = arg1; + }, + __wbg_set_buffer_c3410572051920ba: function(arg0, arg1) { + arg0.buffer = arg1; + }, + __wbg_set_buffer_ef7f75306cf663ed: function(arg0, arg1) { + arg0.buffer = arg1; + }, + __wbg_set_buffers_7d0d8f507699e956: function(arg0, arg1, arg2) { + arg0.buffers = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_bytes_per_row_c54ca96953f35774: function(arg0, arg1) { + arg0.bytesPerRow = arg1 >>> 0; + }, + __wbg_set_bytes_per_row_d69b88eee3929c07: function(arg0, arg1) { + arg0.bytesPerRow = arg1 >>> 0; + }, + __wbg_set_cache_b4a740b195c051f4: function(arg0, arg1) { + arg0.cache = __wbindgen_enum_RequestCache[arg1]; + }, + __wbg_set_className_e0b1e805ac9ecbf4: function(arg0, arg1, arg2) { + arg0.className = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_clear_value_gpu_color_dict_6211425789c76e59: function(arg0, arg1) { + arg0.clearValue = arg1; + }, + __wbg_set_code_b4f37f81f45b5b25: function(arg0, arg1, arg2) { + arg0.code = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_codec_73803202999ff3ab: function(arg0, arg1, arg2) { + arg0.codec = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_coded_height_5cf9f82bfb61b098: function(arg0, arg1) { + arg0.codedHeight = arg1 >>> 0; + }, + __wbg_set_coded_width_e7ada372e1276c51: function(arg0, arg1) { + arg0.codedWidth = arg1 >>> 0; + }, + __wbg_set_color_83aa977526e88cbb: function(arg0, arg1) { + arg0.color = arg1; + }, + __wbg_set_color_attachments_581fdb3310e4abfa: function(arg0, arg1, arg2) { + arg0.colorAttachments = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_compare_cd9b62cdb92eb580: function(arg0, arg1) { + arg0.compare = __wbindgen_enum_GpuCompareFunction[arg1]; + }, + __wbg_set_compare_f36b34abfaa08ccb: function(arg0, arg1) { + arg0.compare = __wbindgen_enum_GpuCompareFunction[arg1]; + }, + __wbg_set_count_069a4eac409bac55: function(arg0, arg1) { + arg0.count = arg1 >>> 0; + }, + __wbg_set_create_a807a6e9ac628698: function(arg0, arg1) { + arg0.create = arg1 !== 0; + }, + __wbg_set_create_fa1dfa475fac91e9: function(arg0, arg1) { + arg0.create = arg1 !== 0; + }, + __wbg_set_credentials_bb34a40189e3b43b: function(arg0, arg1) { + arg0.credentials = __wbindgen_enum_RequestCredentials[arg1]; + }, + __wbg_set_cull_mode_fc649853947a3d0c: function(arg0, arg1) { + arg0.cullMode = __wbindgen_enum_GpuCullMode[arg1]; + }, + __wbg_set_data_353846afd42a10b1: function(arg0, arg1) { + arg0.data = arg1; + }, + __wbg_set_depth_bias_clamp_1c0d695df7f092e5: function(arg0, arg1) { + arg0.depthBiasClamp = arg1; + }, + __wbg_set_depth_bias_d7cd16096242a657: function(arg0, arg1) { + arg0.depthBias = arg1; + }, + __wbg_set_depth_bias_slope_scale_c4e52ec743ef55ba: function(arg0, arg1) { + arg0.depthBiasSlopeScale = arg1; + }, + __wbg_set_depth_clear_value_beda3ec5b1a5c43a: function(arg0, arg1) { + arg0.depthClearValue = arg1; + }, + __wbg_set_depth_compare_0c8631eb2eae98e3: function(arg0, arg1) { + arg0.depthCompare = __wbindgen_enum_GpuCompareFunction[arg1]; + }, + __wbg_set_depth_fail_op_668155ae33d3c06f: function(arg0, arg1) { + arg0.depthFailOp = __wbindgen_enum_GpuStencilOperation[arg1]; + }, + __wbg_set_depth_load_op_511c513eab4e56a9: function(arg0, arg1) { + arg0.depthLoadOp = __wbindgen_enum_GpuLoadOp[arg1]; + }, + __wbg_set_depth_or_array_layers_89371305ed0bd962: function(arg0, arg1) { + arg0.depthOrArrayLayers = arg1 >>> 0; + }, + __wbg_set_depth_read_only_7f41a74741c144ec: function(arg0, arg1) { + arg0.depthReadOnly = arg1 !== 0; + }, + __wbg_set_depth_stencil_97506c7bea4f53da: function(arg0, arg1) { + arg0.depthStencil = arg1; + }, + __wbg_set_depth_stencil_attachment_73b79e8b4e948222: function(arg0, arg1) { + arg0.depthStencilAttachment = arg1; + }, + __wbg_set_depth_store_op_c89f33b39b43361c: function(arg0, arg1) { + arg0.depthStoreOp = __wbindgen_enum_GpuStoreOp[arg1]; + }, + __wbg_set_depth_write_enabled_ce89750042940350: function(arg0, arg1) { + arg0.depthWriteEnabled = arg1 !== 0; + }, + __wbg_set_description_1ca246c5902e3ff8: function(arg0, arg1) { + arg0.description = arg1; + }, + __wbg_set_device_e275d1d4f3c9eb74: function(arg0, arg1) { + arg0.device = arg1; + }, + __wbg_set_dimension_868eee80f4b90011: function(arg0, arg1) { + arg0.dimension = __wbindgen_enum_GpuTextureDimension[arg1]; + }, + __wbg_set_dimension_e325282e613ca0a4: function(arg0, arg1) { + arg0.dimension = __wbindgen_enum_GpuTextureViewDimension[arg1]; + }, + __wbg_set_download_67c3dbb2b32b18d0: function(arg0, arg1, arg2) { + arg0.download = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_dst_factor_ec7407f19be1aff9: function(arg0, arg1) { + arg0.dstFactor = __wbindgen_enum_GpuBlendFactor[arg1]; + }, + __wbg_set_duration_f64_d850447775429a67: function(arg0, arg1) { + arg0.duration = arg1; + }, + __wbg_set_end_of_pass_write_index_0d546e46b86ea069: function(arg0, arg1) { + arg0.endOfPassWriteIndex = arg1 >>> 0; + }, + __wbg_set_entries_86a29dd6291c95e7: function(arg0, arg1, arg2) { + arg0.entries = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_entries_a12aca1e458b0456: function(arg0, arg1, arg2) { + arg0.entries = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_entry_point_207540f042015ce5: function(arg0, arg1, arg2) { + arg0.entryPoint = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_entry_point_e87e79251dd3144f: function(arg0, arg1, arg2) { + arg0.entryPoint = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_error_e0fdb5ef69612720: function(arg0, arg1) { + arg0.error = arg1; + }, + __wbg_set_external_texture_386483d8dd82ab56: function(arg0, arg1) { + arg0.externalTexture = arg1; + }, + __wbg_set_fail_op_92f716dbc88b6973: function(arg0, arg1) { + arg0.failOp = __wbindgen_enum_GpuStencilOperation[arg1]; + }, + __wbg_set_flip_y_4e1632b36ad0413a: function(arg0, arg1) { + arg0.flipY = arg1 !== 0; + }, + __wbg_set_format_1fcaa7d60546b490: function(arg0, arg1) { + arg0.format = __wbindgen_enum_GpuTextureFormat[arg1]; + }, + __wbg_set_format_2c1414a817c213f8: function(arg0, arg1) { + arg0.format = __wbindgen_enum_GpuTextureFormat[arg1]; + }, + __wbg_set_format_533f9ffa7eef563d: function(arg0, arg1) { + arg0.format = __wbindgen_enum_GpuTextureFormat[arg1]; + }, + __wbg_set_format_5d2f25cc93654ecc: function(arg0, arg1) { + arg0.format = __wbindgen_enum_GpuVertexFormat[arg1]; + }, + __wbg_set_format_5ff53724ed6cedf2: function(arg0, arg1) { + arg0.format = __wbindgen_enum_GpuTextureFormat[arg1]; + }, + __wbg_set_format_815efd4dc4817bbb: function(arg0, arg1) { + arg0.format = __wbindgen_enum_GpuTextureFormat[arg1]; + }, + __wbg_set_format_e52bdcca880d2c8e: function(arg0, arg1) { + arg0.format = __wbindgen_enum_GpuTextureFormat[arg1]; + }, + __wbg_set_fragment_8b780f00a0b0e6f3: function(arg0, arg1) { + arg0.fragment = arg1; + }, + __wbg_set_front_face_28ffdf524eedce5b: function(arg0, arg1) { + arg0.frontFace = __wbindgen_enum_GpuFrontFace[arg1]; + }, + __wbg_set_g_5983abfc46e0cf4e: function(arg0, arg1) { + arg0.g = arg1; + }, + __wbg_set_hardware_acceleration_e0184d39413599b8: function(arg0, arg1) { + arg0.hardwareAcceleration = __wbindgen_enum_HardwareAcceleration[arg1]; + }, + __wbg_set_has_dynamic_offset_62bc230bdb7c54d0: function(arg0, arg1) { + arg0.hasDynamicOffset = arg1 !== 0; + }, + __wbg_set_headers_9c61d123c3ee1f10: function(arg0, arg1) { + arg0.headers = arg1; + }, + __wbg_set_height_14335c4047cf9c1b: function(arg0, arg1) { + arg0.height = arg1 >>> 0; + }, + __wbg_set_height_7d9d8f892e6964c6: function(arg0, arg1) { + arg0.height = arg1 >>> 0; + }, + __wbg_set_height_bbeef8f354041577: function(arg0, arg1) { + arg0.height = arg1 >>> 0; + }, + __wbg_set_href_25786788ec7ffedd: function(arg0, arg1, arg2) { + arg0.href = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_id_4beae8b813c092d8: function(arg0, arg1, arg2) { + arg0.id = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_innerHTML_f78a45a07f97e136: function(arg0, arg1, arg2) { + arg0.innerHTML = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_innerText_847403b9d4f38f77: function(arg0, arg1, arg2) { + arg0.innerText = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_integrity_13c390f33acee59c: function(arg0, arg1, arg2) { + arg0.integrity = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_08d9be3e4719c226: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_17eb9fe3a02f62b0: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_48e6b787d256f621: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_547d0d4aec39fbe9: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_5ee7427342869829: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_60ad96c811e0d109: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_72bb4f41ef0cb893: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_79387decda299036: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_9556af8b5cda3c9d: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_d010f237b26f2c55: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_e16e2dbe51349c7f: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_e3944e54881b8c50: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_label_e922700240417ab5: function(arg0, arg1, arg2) { + arg0.label = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_layout_50ab727f44b38f26: function(arg0, arg1) { + arg0.layout = arg1; + }, + __wbg_set_layout_913d53c17194c989: function(arg0, arg1) { + arg0.layout = arg1; + }, + __wbg_set_layout_gpu_auto_layout_mode_aeba193938b47882: function(arg0, arg1) { + arg0.layout = __wbindgen_enum_GpuAutoLayoutMode[arg1]; + }, + __wbg_set_load_op_99661da6c4eab9b0: function(arg0, arg1) { + arg0.loadOp = __wbindgen_enum_GpuLoadOp[arg1]; + }, + __wbg_set_lod_max_clamp_dd2d9f9f052f4f44: function(arg0, arg1) { + arg0.lodMaxClamp = arg1; + }, + __wbg_set_lod_min_clamp_6d20c97916baeb93: function(arg0, arg1) { + arg0.lodMinClamp = arg1; + }, + __wbg_set_mag_filter_b5adebc99cb938e1: function(arg0, arg1) { + arg0.magFilter = __wbindgen_enum_GpuFilterMode[arg1]; + }, + __wbg_set_mapped_at_creation_81b586dc90a50347: function(arg0, arg1) { + arg0.mappedAtCreation = arg1 !== 0; + }, + __wbg_set_mask_70a8a59ce09e5997: function(arg0, arg1) { + arg0.mask = arg1 >>> 0; + }, + __wbg_set_max_anisotropy_2beada0e2db62c45: function(arg0, arg1) { + arg0.maxAnisotropy = arg1; + }, + __wbg_set_method_5532d59b92d76467: function(arg0, arg1, arg2) { + arg0.method = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_min_binding_size_f64_5005a6904cdf43da: function(arg0, arg1) { + arg0.minBindingSize = arg1; + }, + __wbg_set_min_filter_c72f17375e135f0a: function(arg0, arg1) { + arg0.minFilter = __wbindgen_enum_GpuFilterMode[arg1]; + }, + __wbg_set_mip_level_13253f3afc7aa58a: function(arg0, arg1) { + arg0.mipLevel = arg1 >>> 0; + }, + __wbg_set_mip_level_count_534caaa7e68e68b8: function(arg0, arg1) { + arg0.mipLevelCount = arg1 >>> 0; + }, + __wbg_set_mip_level_count_776c8c218b65bc08: function(arg0, arg1) { + arg0.mipLevelCount = arg1 >>> 0; + }, + __wbg_set_mip_level_f7ac79e8c54f59ad: function(arg0, arg1) { + arg0.mipLevel = arg1 >>> 0; + }, + __wbg_set_mipmap_filter_5bf66195a3639700: function(arg0, arg1) { + arg0.mipmapFilter = __wbindgen_enum_GpuMipmapFilterMode[arg1]; + }, + __wbg_set_mode_66c79886ad78fc05: function(arg0, arg1) { + arg0.mode = __wbindgen_enum_RequestMode[arg1]; + }, + __wbg_set_mode_9990b3393ba469ae: function(arg0, arg1) { + arg0.mode = __wbindgen_enum_GpuCanvasToneMappingMode[arg1]; + }, + __wbg_set_module_d0e2098713606cae: function(arg0, arg1) { + arg0.module = arg1; + }, + __wbg_set_module_f02e076ca7e7daf8: function(arg0, arg1) { + arg0.module = arg1; + }, + __wbg_set_multiple_682a70d088570168: function(arg0, arg1) { + arg0.multiple = arg1 !== 0; + }, + __wbg_set_multisample_37ddafe88b5cd466: function(arg0, arg1) { + arg0.multisample = arg1; + }, + __wbg_set_multisampled_7913fd7183272840: function(arg0, arg1) { + arg0.multisampled = arg1 !== 0; + }, + __wbg_set_offset_f64_28c24dc15000932e: function(arg0, arg1) { + arg0.offset = arg1; + }, + __wbg_set_offset_f64_89f0ce01a689839e: function(arg0, arg1) { + arg0.offset = arg1; + }, + __wbg_set_offset_f64_b562d1367e34ef93: function(arg0, arg1) { + arg0.offset = arg1; + }, + __wbg_set_offset_f64_fa66068813376ca3: function(arg0, arg1) { + arg0.offset = arg1; + }, + __wbg_set_once_51a9fb6b8af8a72b: function(arg0, arg1) { + arg0.once = arg1 !== 0; + }, + __wbg_set_onclick_527135192dd54d92: function(arg0, arg1) { + arg0.onclick = arg1; + }, + __wbg_set_onuncapturederror_c8a77eb8695205a0: function(arg0, arg1) { + arg0.onuncapturederror = arg1; + }, + __wbg_set_operation_62ce44e1728c4047: function(arg0, arg1) { + arg0.operation = __wbindgen_enum_GpuBlendOperation[arg1]; + }, + __wbg_set_optimize_for_latency_3e3786621aaf6f56: function(arg0, arg1) { + arg0.optimizeForLatency = arg1 !== 0; + }, + __wbg_set_origin_gpu_origin_2d_dict_1240202973e56f92: function(arg0, arg1) { + arg0.origin = arg1; + }, + __wbg_set_origin_gpu_origin_3d_dict_37222d7b3d238123: function(arg0, arg1) { + arg0.origin = arg1; + }, + __wbg_set_origin_gpu_origin_3d_dict_631c04520718091f: function(arg0, arg1) { + arg0.origin = arg1; + }, + __wbg_set_output_6401c39ffe15258f: function(arg0, arg1) { + arg0.output = arg1; + }, + __wbg_set_pass_op_cf02fa088d6352a7: function(arg0, arg1) { + arg0.passOp = __wbindgen_enum_GpuStencilOperation[arg1]; + }, + __wbg_set_power_preference_8fdca0b7af640d49: function(arg0, arg1) { + arg0.powerPreference = __wbindgen_enum_GpuPowerPreference[arg1]; + }, + __wbg_set_premultiplied_alpha_3f27816ad319d5a9: function(arg0, arg1) { + arg0.premultipliedAlpha = arg1 !== 0; + }, + __wbg_set_prevent_scroll_82778f333ef22ca8: function(arg0, arg1) { + arg0.preventScroll = arg1 !== 0; + }, + __wbg_set_primitive_43c23761a55b4088: function(arg0, arg1) { + arg0.primitive = arg1; + }, + __wbg_set_query_set_41de86d2401aee04: function(arg0, arg1) { + arg0.querySet = arg1; + }, + __wbg_set_r_c6f4c68f4804d655: function(arg0, arg1) { + arg0.r = arg1; + }, + __wbg_set_redirect_badd73a0bcb765e3: function(arg0, arg1) { + arg0.redirect = __wbindgen_enum_RequestRedirect[arg1]; + }, + __wbg_set_referrer_02890bb2de855af1: function(arg0, arg1, arg2) { + arg0.referrer = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_referrer_policy_e49a6d0d7473fd16: function(arg0, arg1) { + arg0.referrerPolicy = __wbindgen_enum_ReferrerPolicy[arg1]; + }, + __wbg_set_required_features_1baf274a8669db60: function(arg0, arg1, arg2) { + arg0.requiredFeatures = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_required_limits_871ed33c68613dcb: function(arg0, arg1) { + arg0.requiredLimits = arg1; + }, + __wbg_set_resolve_target_gpu_texture_view_b19a4f2debf79b96: function(arg0, arg1) { + arg0.resolveTarget = arg1; + }, + __wbg_set_resource_5ae7b5e67924f234: function(arg0, arg1) { + arg0.resource = arg1; + }, + __wbg_set_resource_gpu_buffer_binding_e5dbca063e7cb67b: function(arg0, arg1) { + arg0.resource = arg1; + }, + __wbg_set_resource_gpu_texture_view_eb46c355d51ad7e5: function(arg0, arg1) { + arg0.resource = arg1; + }, + __wbg_set_rows_per_image_5011f97318ee71af: function(arg0, arg1) { + arg0.rowsPerImage = arg1 >>> 0; + }, + __wbg_set_rows_per_image_59a813ac5006e10e: function(arg0, arg1) { + arg0.rowsPerImage = arg1 >>> 0; + }, + __wbg_set_sample_count_eb86a8b18545b54f: function(arg0, arg1) { + arg0.sampleCount = arg1 >>> 0; + }, + __wbg_set_sample_type_c32e1dfff94e63eb: function(arg0, arg1) { + arg0.sampleType = __wbindgen_enum_GpuTextureSampleType[arg1]; + }, + __wbg_set_sampler_c0e1258543a33bce: function(arg0, arg1) { + arg0.sampler = arg1; + }, + __wbg_set_shader_location_7e1832a74f912217: function(arg0, arg1) { + arg0.shaderLocation = arg1 >>> 0; + }, + __wbg_set_signal_c4ef8faddb4c1446: function(arg0, arg1) { + arg0.signal = arg1; + }, + __wbg_set_size_f64_6bcd40704bf4cfdc: function(arg0, arg1) { + arg0.size = arg1; + }, + __wbg_set_size_f64_8b8f6bba5d678162: function(arg0, arg1) { + arg0.size = arg1; + }, + __wbg_set_size_gpu_extent_3d_dict_7e42e1c98fa36434: function(arg0, arg1) { + arg0.size = arg1; + }, + __wbg_set_source_0c40b87cfdd5d704: function(arg0, arg1) { + arg0.source = arg1; + }, + __wbg_set_source_html_canvas_element_f657e39507ba2fe5: function(arg0, arg1) { + arg0.source = arg1; + }, + __wbg_set_source_html_image_element_fcc7cba0635adac1: function(arg0, arg1) { + arg0.source = arg1; + }, + __wbg_set_source_html_video_element_e63a39653665f651: function(arg0, arg1) { + arg0.source = arg1; + }, + __wbg_set_source_image_data_68478f1afce208b8: function(arg0, arg1) { + arg0.source = arg1; + }, + __wbg_set_source_offscreen_canvas_266a3de949693e62: function(arg0, arg1) { + arg0.source = arg1; + }, + __wbg_set_source_video_frame_3083c0ce54b9cb73: function(arg0, arg1) { + arg0.source = arg1; + }, + __wbg_set_src_factor_9bfe84af9b7b5cac: function(arg0, arg1) { + arg0.srcFactor = __wbindgen_enum_GpuBlendFactor[arg1]; + }, + __wbg_set_stencil_back_85b22f1db5b1940a: function(arg0, arg1) { + arg0.stencilBack = arg1; + }, + __wbg_set_stencil_clear_value_42be608809151e2a: function(arg0, arg1) { + arg0.stencilClearValue = arg1 >>> 0; + }, + __wbg_set_stencil_front_525526164a798a44: function(arg0, arg1) { + arg0.stencilFront = arg1; + }, + __wbg_set_stencil_load_op_31838c036993098a: function(arg0, arg1) { + arg0.stencilLoadOp = __wbindgen_enum_GpuLoadOp[arg1]; + }, + __wbg_set_stencil_read_mask_5cc26495e8b3ae82: function(arg0, arg1) { + arg0.stencilReadMask = arg1 >>> 0; + }, + __wbg_set_stencil_read_only_bf1d0c1897e25c62: function(arg0, arg1) { + arg0.stencilReadOnly = arg1 !== 0; + }, + __wbg_set_stencil_store_op_e6be1cbc3a8fc210: function(arg0, arg1) { + arg0.stencilStoreOp = __wbindgen_enum_GpuStoreOp[arg1]; + }, + __wbg_set_stencil_write_mask_d9cb40ec4b4bee5b: function(arg0, arg1) { + arg0.stencilWriteMask = arg1 >>> 0; + }, + __wbg_set_step_mode_a97bb24714da41a9: function(arg0, arg1) { + arg0.stepMode = __wbindgen_enum_GpuVertexStepMode[arg1]; + }, + __wbg_set_storage_texture_939a097db4b18bd4: function(arg0, arg1) { + arg0.storageTexture = arg1; + }, + __wbg_set_store_op_b5fdf672436f13f3: function(arg0, arg1) { + arg0.storeOp = __wbindgen_enum_GpuStoreOp[arg1]; + }, + __wbg_set_strip_index_format_9f787be6c5fc9e87: function(arg0, arg1) { + arg0.stripIndexFormat = __wbindgen_enum_GpuIndexFormat[arg1]; + }, + __wbg_set_tabIndex_70047c7d062bb928: function(arg0, arg1) { + arg0.tabIndex = arg1; + }, + __wbg_set_targets_c38bd200c836d66f: function(arg0, arg1, arg2) { + arg0.targets = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_texture_016561d5911339e5: function(arg0, arg1) { + arg0.texture = arg1; + }, + __wbg_set_texture_1f64653a5d2d7b4d: function(arg0, arg1) { + arg0.texture = arg1; + }, + __wbg_set_texture_9dcedde1bb31eda6: function(arg0, arg1) { + arg0.texture = arg1; + }, + __wbg_set_timestamp_244da668ac8da67a: function(arg0, arg1) { + arg0.timestamp = arg1; + }, + __wbg_set_timestamp_25d4d8d3cdbe80a2: function(arg0, arg1) { + arg0.timestamp = arg1; + }, + __wbg_set_timestamp_f64_47419c7ea896d17c: function(arg0, arg1) { + arg0.timestamp = arg1; + }, + __wbg_set_timestamp_writes_98bed1a8bbc6682d: function(arg0, arg1) { + arg0.timestampWrites = arg1; + }, + __wbg_set_tone_mapping_b3464f1baa4cff92: function(arg0, arg1) { + arg0.toneMapping = arg1; + }, + __wbg_set_topology_da25f2cc5af203d2: function(arg0, arg1) { + arg0.topology = __wbindgen_enum_GpuPrimitiveTopology[arg1]; + }, + __wbg_set_type_57a3257a711da878: function(arg0, arg1) { + arg0.type = __wbindgen_enum_EncodedVideoChunkType[arg1]; + }, + __wbg_set_type_8ce203e412e28cf6: function(arg0, arg1, arg2) { + arg0.type = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_type_ccf8472d40abcddf: function(arg0, arg1) { + arg0.type = __wbindgen_enum_GpuSamplerBindingType[arg1]; + }, + __wbg_set_type_d09829f59932a0fc: function(arg0, arg1) { + arg0.type = __wbindgen_enum_GpuBufferBindingType[arg1]; + }, + __wbg_set_type_d2a9a3c584ce2f9a: function(arg0, arg1, arg2) { + arg0.type = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_unclipped_depth_04524a2b44e1e3c1: function(arg0, arg1) { + arg0.unclippedDepth = arg1 !== 0; + }, + __wbg_set_usage_a137f82ca163b0a9: function(arg0, arg1) { + arg0.usage = arg1 >>> 0; + }, + __wbg_set_usage_b2a2935f37bf3d08: function(arg0, arg1) { + arg0.usage = arg1 >>> 0; + }, + __wbg_set_usage_ba5b0f8b333ab325: function(arg0, arg1) { + arg0.usage = arg1 >>> 0; + }, + __wbg_set_usage_ddd42599bbba7779: function(arg0, arg1) { + arg0.usage = arg1 >>> 0; + }, + __wbg_set_value_e5d078763e63e81e: function(arg0, arg1, arg2) { + arg0.value = getStringFromWasm0(arg1, arg2); + }, + __wbg_set_vertex_0be5d146f9ff6f36: function(arg0, arg1) { + arg0.vertex = arg1; + }, + __wbg_set_view_dimension_0df554032f1f3a85: function(arg0, arg1) { + arg0.viewDimension = __wbindgen_enum_GpuTextureViewDimension[arg1]; + }, + __wbg_set_view_dimension_4818d4c18ce5815e: function(arg0, arg1) { + arg0.viewDimension = __wbindgen_enum_GpuTextureViewDimension[arg1]; + }, + __wbg_set_view_formats_4347dc8363331086: function(arg0, arg1, arg2) { + arg0.viewFormats = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_view_formats_5797d2fff3c11808: function(arg0, arg1, arg2) { + arg0.viewFormats = getArrayJsValueViewFromWasm0(arg1, arg2); + }, + __wbg_set_view_gpu_texture_view_9b2d86b6b99d9fd9: function(arg0, arg1) { + arg0.view = arg1; + }, + __wbg_set_view_gpu_texture_view_c0f35f8857c25206: function(arg0, arg1) { + arg0.view = arg1; + }, + __wbg_set_visibility_9570b037224c4cc2: function(arg0, arg1) { + arg0.visibility = arg1 >>> 0; + }, + __wbg_set_width_49ac9b7d914afc85: function(arg0, arg1) { + arg0.width = arg1 >>> 0; + }, + __wbg_set_width_8e30d010cd66830d: function(arg0, arg1) { + arg0.width = arg1 >>> 0; + }, + __wbg_set_width_9f685402c2cbee70: function(arg0, arg1) { + arg0.width = arg1 >>> 0; + }, + __wbg_set_write_mask_d45279e56abbfcb5: function(arg0, arg1) { + arg0.writeMask = arg1 >>> 0; + }, + __wbg_set_x_232d24fdc32d8351: function(arg0, arg1) { + arg0.x = arg1 >>> 0; + }, + __wbg_set_x_876d592971db129a: function(arg0, arg1) { + arg0.x = arg1 >>> 0; + }, + __wbg_set_y_18fe375093e59dfb: function(arg0, arg1) { + arg0.y = arg1 >>> 0; + }, + __wbg_set_y_2b1f5ac0dd5586a5: function(arg0, arg1) { + arg0.y = arg1 >>> 0; + }, + __wbg_set_z_ef005d82bc9d24e3: function(arg0, arg1) { + arg0.z = arg1 >>> 0; + }, + __wbg_shaderSource_4cf90af97621ff49: function(arg0, arg1, arg2, arg3) { + arg0.shaderSource(arg1, getStringFromWasm0(arg2, arg3)); + }, + __wbg_shaderSource_c3469dc2221dd528: function(arg0, arg1, arg2, arg3) { + arg0.shaderSource(arg1, getStringFromWasm0(arg2, arg3)); + }, + __wbg_shiftKey_9bcb8bdd60c2f152: function(arg0) { + const ret = arg0.shiftKey; + return ret; + }, + __wbg_shiftKey_9f797da486b2ade8: function(arg0) { + const ret = arg0.shiftKey; + return ret; + }, + __wbg_signal_dad7cb35193abd31: function(arg0) { + const ret = arg0.signal; + return ret; + }, + __wbg_size_6304a694765921a9: function(arg0) { + const ret = arg0.size; + return ret; + }, + __wbg_size_79acc354d385bbfe: function(arg0) { + const ret = arg0.size; + return ret; + }, + __wbg_slice_2b88ff0ac64039d6: function(arg0, arg1) { + const ret = arg1.slice(); + const ptr1 = passArrayJsValueToWasm0(ret, wasm.__wbindgen_malloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_slice_7c7553e3b38b0ddb: function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.slice(arg1, arg2); + return ret; + }, arguments); }, + __wbg_stack_8e147b7d6fdc29c4: function(arg0, arg1) { + const ret = arg1.stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_stack_9ed1bd1924f4f869: function(arg0, arg1) { + const ret = arg1.stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_state_c83ca713bb9cda98: function(arg0) { + const ret = arg0.state; + return ret; + }, + __wbg_state_d4cb8ed54a665e6e: function(arg0) { + const ret = arg0.state; + return (__wbindgen_enum_CodecState.indexOf(ret) + 1 || 4) - 1; + }, + __wbg_state_edcc5b2da67f07f2: function() { return handleError(function (arg0) { + const ret = arg0.state; + return ret; + }, arguments); }, + __wbg_static_accessor_GLOBAL_4ef717fb391d88b7: function() { + const ret = typeof global === 'undefined' ? null : global; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_static_accessor_GLOBAL_THIS_8d1badc68b5a74f4: function() { + const ret = typeof globalThis === 'undefined' ? null : globalThis; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_static_accessor_SELF_146583524fe1469b: function() { + const ret = typeof self === 'undefined' ? null : self; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_static_accessor_WINDOW_f2829a2234d7819e: function() { + const ret = typeof window === 'undefined' ? null : window; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_statusText_9f08c32741a99815: function(arg0, arg1) { + const ret = arg1.statusText; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_status_c45b3b9b3033184a: function(arg0) { + const ret = arg0.status; + return ret; + }, + __wbg_stencilFuncSeparate_35136c4e5153406f: function(arg0, arg1, arg2, arg3, arg4) { + arg0.stencilFuncSeparate(arg1 >>> 0, arg2 >>> 0, arg3, arg4 >>> 0); + }, + __wbg_stencilFuncSeparate_814300446c2969ef: function(arg0, arg1, arg2, arg3, arg4) { + arg0.stencilFuncSeparate(arg1 >>> 0, arg2 >>> 0, arg3, arg4 >>> 0); + }, + __wbg_stencilMaskSeparate_49367b0b5883a8bd: function(arg0, arg1, arg2) { + arg0.stencilMaskSeparate(arg1 >>> 0, arg2 >>> 0); + }, + __wbg_stencilMaskSeparate_63976cc45fb94d84: function(arg0, arg1, arg2) { + arg0.stencilMaskSeparate(arg1 >>> 0, arg2 >>> 0); + }, + __wbg_stencilMask_1c99b79b516d12dd: function(arg0, arg1) { + arg0.stencilMask(arg1 >>> 0); + }, + __wbg_stencilMask_9a844dc58a89992f: function(arg0, arg1) { + arg0.stencilMask(arg1 >>> 0); + }, + __wbg_stencilOpSeparate_b2cb9af05b803e02: function(arg0, arg1, arg2, arg3, arg4) { + arg0.stencilOpSeparate(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4 >>> 0); + }, + __wbg_stencilOpSeparate_c77fcb47561d0aee: function(arg0, arg1, arg2, arg3, arg4) { + arg0.stencilOpSeparate(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0, arg4 >>> 0); + }, + __wbg_stopPropagation_4c4ff88c29f9bc38: function(arg0) { + arg0.stopPropagation(); + }, + __wbg_storage_756400487605531a: function(arg0) { + const ret = arg0.storage; + return ret; + }, + __wbg_stream_0ea97b74f081f92b: function(arg0) { + const ret = arg0.stream(); + return ret; + }, + __wbg_stringify_b54333f60f1e4dad: function() { return handleError(function (arg0) { + const ret = JSON.stringify(arg0); + return ret; + }, arguments); }, + __wbg_structuredClone_7011e154de89acbe: function() { return handleError(function (arg0) { + const ret = window.structuredClone(arg0); + return ret; + }, arguments); }, + __wbg_style_6657aed849e5d757: function(arg0) { + const ret = arg0.style; + return ret; + }, + __wbg_subgroupMaxSize_1527c5f7a8fe91bb: function(arg0) { + const ret = arg0.subgroupMaxSize; + return ret; + }, + __wbg_subgroupMinSize_d6c5ad4bddc828e9: function(arg0) { + const ret = arg0.subgroupMinSize; + return ret; + }, + __wbg_submit_ce44115121cd166c: function(arg0, arg1, arg2) { + arg0.submit(getArrayJsValueViewFromWasm0(arg1, arg2)); + }, + __wbg_texImage2D_3813406af5bf54c8: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texImage2D_5abd8779d1d033c7: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texImage2D_8d168171984f2a40: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texImage3D_bdd9bebe42ed1f52: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) { + arg0.texImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8 >>> 0, arg9 >>> 0, arg10); + }, arguments); }, + __wbg_texImage3D_ef16a1f721b3f908: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) { + arg0.texImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8 >>> 0, arg9 >>> 0, arg10); + }, arguments); }, + __wbg_texParameteri_1fc451e0964fc91c: function(arg0, arg1, arg2, arg3) { + arg0.texParameteri(arg1 >>> 0, arg2 >>> 0, arg3); + }, + __wbg_texParameteri_9d0daa263d3a863f: function(arg0, arg1, arg2, arg3) { + arg0.texParameteri(arg1 >>> 0, arg2 >>> 0, arg3); + }, + __wbg_texStorage2D_7f947efc63dac273: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.texStorage2D(arg1 >>> 0, arg2, arg3 >>> 0, arg4, arg5); + }, + __wbg_texStorage3D_f8f2e4b3386736f9: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + arg0.texStorage3D(arg1 >>> 0, arg2, arg3 >>> 0, arg4, arg5, arg6); + }, + __wbg_texSubImage2D_047380bb2660e4f9: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texSubImage2D_5058af3d30a8e205: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texSubImage2D_6a376bfc3a31436b: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texSubImage2D_98c43894eb217aa7: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texSubImage2D_bed5e7a3cd81d409: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texSubImage2D_cccafa6de64f2781: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texSubImage2D_d1af697e69f8a9e4: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texSubImage2D_d3cd09d0ffcb27be: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texSubImage2D_e107b4f88c19b920: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { + arg0.texSubImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9); + }, arguments); }, + __wbg_texSubImage3D_3711a86f03ffceef: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.texSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10 >>> 0, arg11); + }, arguments); }, + __wbg_texSubImage3D_45e498ae6298998c: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.texSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10 >>> 0, arg11); + }, arguments); }, + __wbg_texSubImage3D_4fdd4cd95a2925c2: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.texSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10 >>> 0, arg11); + }, arguments); }, + __wbg_texSubImage3D_6cb6cfd732dad145: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.texSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10 >>> 0, arg11); + }, arguments); }, + __wbg_texSubImage3D_8077e90ec309c414: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.texSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10 >>> 0, arg11); + }, arguments); }, + __wbg_texSubImage3D_93b38c69acb735c8: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.texSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10 >>> 0, arg11); + }, arguments); }, + __wbg_texSubImage3D_c9e5a071796d412f: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.texSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10 >>> 0, arg11); + }, arguments); }, + __wbg_texSubImage3D_feebaf7f0f4594c6: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) { + arg0.texSubImage3D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 >>> 0, arg10 >>> 0, arg11); + }, arguments); }, + __wbg_then_16d107c451e9905d: function(arg0, arg1, arg2) { + const ret = arg0.then(arg1, arg2); + return ret; + }, + __wbg_then_6ec10ae38b3e92f7: function(arg0, arg1) { + const ret = arg0.then(arg1); + return ret; + }, + __wbg_timestamp_263c471d3598c2cc: function(arg0) { + const ret = arg0.timestamp; + return ret; + }, + __wbg_toString_b201c2690bbe445a: function(arg0) { + const ret = arg0.toString(); + return ret; + }, + __wbg_toString_bac9199ff382784d: function(arg0) { + const ret = arg0.toString(); + return ret; + }, + __wbg_top_fe120acfa924a430: function(arg0) { + const ret = arg0.top; + return ret; + }, + __wbg_touches_a631c50f1b367753: function(arg0) { + const ret = arg0.touches; + return ret; + }, + __wbg_type_fa708ab6c8b1b8ab: function(arg0, arg1) { + const ret = arg1.type; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_unconfigure_0a07a0a40de8988d: function(arg0) { + arg0.unconfigure(); + }, + __wbg_uniform1f_62692c8fa8e7bf1e: function(arg0, arg1, arg2) { + arg0.uniform1f(arg1, arg2); + }, + __wbg_uniform1f_b79d0c5667f9fb40: function(arg0, arg1, arg2) { + arg0.uniform1f(arg1, arg2); + }, + __wbg_uniform1i_5830de6702add20a: function(arg0, arg1, arg2) { + arg0.uniform1i(arg1, arg2); + }, + __wbg_uniform1i_7621f908f78177df: function(arg0, arg1, arg2) { + arg0.uniform1i(arg1, arg2); + }, + __wbg_uniform1ui_cd7ad5581093b3df: function(arg0, arg1, arg2) { + arg0.uniform1ui(arg1, arg2 >>> 0); + }, + __wbg_uniform2fv_1b43656b33177d21: function(arg0, arg1, arg2, arg3) { + arg0.uniform2fv(arg1, getArrayF32FromWasm0(arg2, arg3)); + }, + __wbg_uniform2fv_948dab6a82b428ac: function(arg0, arg1, arg2, arg3) { + arg0.uniform2fv(arg1, getArrayF32FromWasm0(arg2, arg3)); + }, + __wbg_uniform2iv_859048b9d60f46ae: function(arg0, arg1, arg2, arg3) { + arg0.uniform2iv(arg1, getArrayI32FromWasm0(arg2, arg3)); + }, + __wbg_uniform2iv_f84a24961c0cfcd0: function(arg0, arg1, arg2, arg3) { + arg0.uniform2iv(arg1, getArrayI32FromWasm0(arg2, arg3)); + }, + __wbg_uniform2uiv_8a9cb3155271213b: function(arg0, arg1, arg2, arg3) { + arg0.uniform2uiv(arg1, getArrayU32FromWasm0(arg2, arg3)); + }, + __wbg_uniform3fv_8ecb5ebb510b7bce: function(arg0, arg1, arg2, arg3) { + arg0.uniform3fv(arg1, getArrayF32FromWasm0(arg2, arg3)); + }, + __wbg_uniform3fv_95d1933ea1440725: function(arg0, arg1, arg2, arg3) { + arg0.uniform3fv(arg1, getArrayF32FromWasm0(arg2, arg3)); + }, + __wbg_uniform3iv_09abae5eabd6b9d6: function(arg0, arg1, arg2, arg3) { + arg0.uniform3iv(arg1, getArrayI32FromWasm0(arg2, arg3)); + }, + __wbg_uniform3iv_a3a7008990fd84f0: function(arg0, arg1, arg2, arg3) { + arg0.uniform3iv(arg1, getArrayI32FromWasm0(arg2, arg3)); + }, + __wbg_uniform3uiv_3c0b163732f5b8f0: function(arg0, arg1, arg2, arg3) { + arg0.uniform3uiv(arg1, getArrayU32FromWasm0(arg2, arg3)); + }, + __wbg_uniform4f_9ff60fc65b0ed726: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.uniform4f(arg1, arg2, arg3, arg4, arg5); + }, + __wbg_uniform4f_b25e39808b830021: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.uniform4f(arg1, arg2, arg3, arg4, arg5); + }, + __wbg_uniform4fv_4ca8c114ca3de099: function(arg0, arg1, arg2, arg3) { + arg0.uniform4fv(arg1, getArrayF32FromWasm0(arg2, arg3)); + }, + __wbg_uniform4fv_674a247aeb15012d: function(arg0, arg1, arg2, arg3) { + arg0.uniform4fv(arg1, getArrayF32FromWasm0(arg2, arg3)); + }, + __wbg_uniform4iv_45ab52abcb3f882c: function(arg0, arg1, arg2, arg3) { + arg0.uniform4iv(arg1, getArrayI32FromWasm0(arg2, arg3)); + }, + __wbg_uniform4iv_d02934d7b94df609: function(arg0, arg1, arg2, arg3) { + arg0.uniform4iv(arg1, getArrayI32FromWasm0(arg2, arg3)); + }, + __wbg_uniform4uiv_0d1a8ed214f10c31: function(arg0, arg1, arg2, arg3) { + arg0.uniform4uiv(arg1, getArrayU32FromWasm0(arg2, arg3)); + }, + __wbg_uniformBlockBinding_a9ed6b750199e03c: function(arg0, arg1, arg2, arg3) { + arg0.uniformBlockBinding(arg1, arg2 >>> 0, arg3 >>> 0); + }, + __wbg_uniformMatrix2fv_769725d64641341f: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix2fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix2fv_9284424cc6aac672: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix2fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix2x3fv_dba00c4fc8eefe47: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix2x3fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix2x4fv_d801a561c3c18169: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix2x4fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix3fv_33e96c7d29dc1e22: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix3fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix3fv_568aa181379c8a75: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix3fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix3x2fv_ce43e8186ea60a1e: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix3x2fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix3x4fv_8abccc5745b0dd90: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix3x4fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix4fv_25115a23e04f6db7: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix4fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix4fv_423b958042692150: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix4fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix4x2fv_1ac2bf986a322e3f: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix4x2fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_uniformMatrix4x3fv_8640fa85b90ea910: function(arg0, arg1, arg2, arg3, arg4) { + arg0.uniformMatrix4x3fv(arg1, arg2 !== 0, getArrayF32FromWasm0(arg3, arg4)); + }, + __wbg_unmap_adaf93276fdf9aaf: function(arg0) { + arg0.unmap(); + }, + __wbg_url_abdb8fb08377f8c0: function(arg0, arg1) { + const ret = arg1.url; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_usage_437399485b209edf: function(arg0) { + const ret = arg0.usage; + return ret; + }, + __wbg_useProgram_182d120fe476921b: function(arg0, arg1) { + arg0.useProgram(arg1); + }, + __wbg_useProgram_49495850b446fa56: function(arg0, arg1) { + arg0.useProgram(arg1); + }, + __wbg_userActivation_61228a123251b08f: function(arg0) { + const ret = arg0.userActivation; + return ret; + }, + __wbg_userAgent_0558f0ac642f7771: function() { return handleError(function (arg0, arg1) { + const ret = arg1.userAgent; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments); }, + __wbg_valueOf_64f89f12f08671ee: function(arg0) { + const ret = arg0.valueOf(); + return ret; + }, + __wbg_value_1f687dfa7d6c3d08: function(arg0, arg1) { + const ret = arg1.value; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_value_a5d5488a9589444a: function(arg0) { + const ret = arg0.value; + return ret; + }, + __wbg_vertexAttribDivisorANGLE_978337b09d11ed84: function(arg0, arg1, arg2) { + arg0.vertexAttribDivisorANGLE(arg1 >>> 0, arg2 >>> 0); + }, + __wbg_vertexAttribDivisor_fb31b5ed9bc856da: function(arg0, arg1, arg2) { + arg0.vertexAttribDivisor(arg1 >>> 0, arg2 >>> 0); + }, + __wbg_vertexAttribIPointer_de08a8d8b625e253: function(arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.vertexAttribIPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4, arg5); + }, + __wbg_vertexAttribPointer_a8f0af57269c2067: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + arg0.vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6); + }, + __wbg_vertexAttribPointer_b300c8e000cdac93: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + arg0.vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6); + }, + __wbg_videoHeight_1420ccecd0b8b9a1: function(arg0) { + const ret = arg0.videoHeight; + return ret; + }, + __wbg_videoWidth_3c582f863b387cd5: function(arg0) { + const ret = arg0.videoWidth; + return ret; + }, + __wbg_view_21f1d4a4f175dfa9: function(arg0) { + const ret = arg0.view; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, + __wbg_viewport_affdf15c559df1e2: function(arg0, arg1, arg2, arg3, arg4) { + arg0.viewport(arg1, arg2, arg3, arg4); + }, + __wbg_viewport_e8a16ca4a5085e5f: function(arg0, arg1, arg2, arg3, arg4) { + arg0.viewport(arg1, arg2, arg3, arg4); + }, + __wbg_warn_77c4eb4a21e10a21: function(arg0, arg1, arg2, arg3) { + console.warn(arg0, arg1, arg2, arg3); + }, + __wbg_warn_b1370d804fa3e259: function(arg0) { + console.warn(arg0); + }, + __wbg_width_05a6fecf7eca198d: function(arg0) { + const ret = arg0.width; + return ret; + }, + __wbg_width_20c45c895834b83f: function(arg0) { + const ret = arg0.width; + return ret; + }, + __wbg_width_6d9315ecc7140ff6: function(arg0) { + const ret = arg0.width; + return ret; + }, + __wbg_width_c1e3781335067e0c: function(arg0) { + const ret = arg0.width; + return ret; + }, + __wbg_width_d2f212a0df13e242: function(arg0) { + const ret = arg0.width; + return ret; + }, + __wbg_width_f9b3cbe357a34b85: function(arg0) { + const ret = arg0.width; + return ret; + }, + __wbg_writeBuffer_8b5bd251a89198bc: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + arg0.writeBuffer(arg1, arg2, getArrayU8FromWasm0(arg3, arg4), arg5, arg6); + }, arguments); }, + __wbg_writeText_34bfead2ae78e5bb: function(arg0, arg1, arg2) { + const ret = arg0.writeText(getStringFromWasm0(arg1, arg2)); + return ret; + }, + __wbg_writeTexture_53ba204c494b042c: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) { + arg0.writeTexture(arg1, getArrayU8FromWasm0(arg2, arg3), arg4, arg5); + }, arguments); }, + __wbg_write_2d484d0dddfacea9: function(arg0, arg1) { + const ret = arg0.write(arg1); + return ret; + }, + __wbindgen_cast_0000000000000001: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 35001, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h72e0675ea71ceaf9); + return ret; + }, + __wbindgen_cast_0000000000000002: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 36562, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__ha28703b0fc0ac5f5); + return ret; + }, + __wbindgen_cast_0000000000000003: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Array")], shim_idx: 25001, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h76e373640bdbaace); + return ret; + }, + __wbindgen_cast_0000000000000004: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Error")], shim_idx: 29826, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h912ec1a6c04fd19b); + return ret; + }, + __wbindgen_cast_0000000000000005: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 25001, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h76e373640bdbaace_4); + return ret; + }, + __wbindgen_cast_0000000000000006: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPUDevice")], shim_idx: 25244, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h17feb392561402d4); + return ret; + }, + __wbindgen_cast_0000000000000007: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPUUncapturedErrorEvent")], shim_idx: 25245, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__ha366fcce789d0db1); + return ret; + }, + __wbindgen_cast_0000000000000008: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("PopStateEvent")], shim_idx: 9, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hd1708d5debff0eb7); + return ret; + }, + __wbindgen_cast_0000000000000009: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("StorageEvent")], shim_idx: 4598, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3d976baa4adbebda); + return ret; + }, + __wbindgen_cast_000000000000000a: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("VideoFrame")], shim_idx: 29826, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h912ec1a6c04fd19b_9); + return ret; + }, + __wbindgen_cast_000000000000000b: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("any")], shim_idx: 25244, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h17feb392561402d4_10); + return ret; + }, + __wbindgen_cast_000000000000000c: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("undefined")], shim_idx: 25244, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h17feb392561402d4_11); + return ret; + }, + __wbindgen_cast_000000000000000d: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("undefined")], shim_idx: 29827, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h68d110bc138a9729); + return ret; + }, + __wbindgen_cast_000000000000000e: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 2063, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0e2ab714131e0149); + return ret; + }, + __wbindgen_cast_000000000000000f: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 25002, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hbf8a291ae3f8f46d); + return ret; + }, + __wbindgen_cast_0000000000000010: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 30439, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2a38a854c15eed3d); + return ret; + }, + __wbindgen_cast_0000000000000011: function(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 7294, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h46084f6dced1097a); + return ret; + }, + __wbindgen_cast_0000000000000012: function(arg0) { + // Cast intrinsic for `F64 -> Externref`. + const ret = arg0; + return ret; + }, + __wbindgen_cast_0000000000000013: function(arg0, arg1) { + // Cast intrinsic for `Ref(Slice(F32)) -> NamedExternref("Float32Array")`. + const ret = getArrayF32FromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_cast_0000000000000014: function(arg0, arg1) { + // Cast intrinsic for `Ref(Slice(I16)) -> NamedExternref("Int16Array")`. + const ret = getArrayI16FromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_cast_0000000000000015: function(arg0, arg1) { + // Cast intrinsic for `Ref(Slice(I32)) -> NamedExternref("Int32Array")`. + const ret = getArrayI32FromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_cast_0000000000000016: function(arg0, arg1) { + // Cast intrinsic for `Ref(Slice(I8)) -> NamedExternref("Int8Array")`. + const ret = getArrayI8FromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_cast_0000000000000017: function(arg0, arg1) { + // Cast intrinsic for `Ref(Slice(U16)) -> NamedExternref("Uint16Array")`. + const ret = getArrayU16FromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_cast_0000000000000018: function(arg0, arg1) { + // Cast intrinsic for `Ref(Slice(U32)) -> NamedExternref("Uint32Array")`. + const ret = getArrayU32FromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_cast_0000000000000019: function(arg0, arg1) { + // Cast intrinsic for `Ref(Slice(U8)) -> NamedExternref("Uint8Array")`. + const ret = getArrayU8FromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_cast_000000000000001a: function(arg0, arg1) { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return ret; + }, + __wbindgen_init_externref_table: function() { + const table = wasm.__wbindgen_externrefs; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + }, + }; + return { + __proto__: null, + "./re_viewer_bg.js": import0, + }; + } + + function wasm_bindgen__convert__closures_____invoke__h0e2ab714131e0149(arg0, arg1) { + wasm.wasm_bindgen__convert__closures_____invoke__h0e2ab714131e0149(arg0, arg1); + } + + function wasm_bindgen__convert__closures_____invoke__h2a38a854c15eed3d(arg0, arg1) { + wasm.wasm_bindgen__convert__closures_____invoke__h2a38a854c15eed3d(arg0, arg1); + } + + function wasm_bindgen__convert__closures_____invoke__h46084f6dced1097a(arg0, arg1) { + wasm.wasm_bindgen__convert__closures_____invoke__h46084f6dced1097a(arg0, arg1); + } + + function wasm_bindgen__convert__closures_____invoke__hbf8a291ae3f8f46d(arg0, arg1) { + const ret = wasm.wasm_bindgen__convert__closures_____invoke__hbf8a291ae3f8f46d(arg0, arg1); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + + function wasm_bindgen__convert__closures_____invoke__h72e0675ea71ceaf9(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h72e0675ea71ceaf9(arg0, arg1, arg2); + } + + function wasm_bindgen__convert__closures_____invoke__h76e373640bdbaace(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h76e373640bdbaace(arg0, arg1, arg2); + } + + function wasm_bindgen__convert__closures_____invoke__h912ec1a6c04fd19b(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h912ec1a6c04fd19b(arg0, arg1, arg2); + } + + function wasm_bindgen__convert__closures_____invoke__h76e373640bdbaace_4(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h76e373640bdbaace_4(arg0, arg1, arg2); + } + + function wasm_bindgen__convert__closures_____invoke__ha366fcce789d0db1(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__ha366fcce789d0db1(arg0, arg1, arg2); + } + + function wasm_bindgen__convert__closures_____invoke__h3d976baa4adbebda(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h3d976baa4adbebda(arg0, arg1, arg2); + } + + function wasm_bindgen__convert__closures_____invoke__h912ec1a6c04fd19b_9(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h912ec1a6c04fd19b_9(arg0, arg1, arg2); + } + + function wasm_bindgen__convert__closures_____invoke__ha28703b0fc0ac5f5(arg0, arg1, arg2) { + const ret = wasm.wasm_bindgen__convert__closures_____invoke__ha28703b0fc0ac5f5(arg0, arg1, arg2); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + + function wasm_bindgen__convert__closures_____invoke__h17feb392561402d4(arg0, arg1, arg2) { + const ret = wasm.wasm_bindgen__convert__closures_____invoke__h17feb392561402d4(arg0, arg1, arg2); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + + function wasm_bindgen__convert__closures_____invoke__hd1708d5debff0eb7(arg0, arg1, arg2) { + const ret = wasm.wasm_bindgen__convert__closures_____invoke__hd1708d5debff0eb7(arg0, arg1, arg2); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + + function wasm_bindgen__convert__closures_____invoke__h17feb392561402d4_10(arg0, arg1, arg2) { + const ret = wasm.wasm_bindgen__convert__closures_____invoke__h17feb392561402d4_10(arg0, arg1, arg2); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + + function wasm_bindgen__convert__closures_____invoke__h17feb392561402d4_11(arg0, arg1, arg2) { + const ret = wasm.wasm_bindgen__convert__closures_____invoke__h17feb392561402d4_11(arg0, arg1, arg2); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + + function wasm_bindgen__convert__closures_____invoke__h68d110bc138a9729(arg0, arg1, arg2) { + const ret = wasm.wasm_bindgen__convert__closures_____invoke__h68d110bc138a9729(arg0, arg1, arg2); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } + } + + function wasm_bindgen__convert__closures_____invoke__h4a090e5af75dc439(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures_____invoke__h4a090e5af75dc439(arg0, arg1, arg2, arg3); + } + + + const __wbindgen_enum_CodecState = ["unconfigured", "configured", "closed"]; + + + const __wbindgen_enum_EncodedVideoChunkType = ["key", "delta"]; + + + const __wbindgen_enum_GpuAddressMode = ["clamp-to-edge", "repeat", "mirror-repeat"]; + + + const __wbindgen_enum_GpuAutoLayoutMode = ["auto"]; + + + const __wbindgen_enum_GpuBlendFactor = ["zero", "one", "src", "one-minus-src", "src-alpha", "one-minus-src-alpha", "dst", "one-minus-dst", "dst-alpha", "one-minus-dst-alpha", "src-alpha-saturated", "constant", "one-minus-constant", "src1", "one-minus-src1", "src1-alpha", "one-minus-src1-alpha"]; + + + const __wbindgen_enum_GpuBlendOperation = ["add", "subtract", "reverse-subtract", "min", "max"]; + + + const __wbindgen_enum_GpuBufferBindingType = ["uniform", "storage", "read-only-storage"]; + + + const __wbindgen_enum_GpuCanvasAlphaMode = ["opaque", "premultiplied"]; + + + const __wbindgen_enum_GpuCanvasToneMappingMode = ["standard", "extended"]; + + + const __wbindgen_enum_GpuCompareFunction = ["never", "less", "equal", "less-equal", "greater", "not-equal", "greater-equal", "always"]; + + + const __wbindgen_enum_GpuCullMode = ["none", "front", "back"]; + + + const __wbindgen_enum_GpuFilterMode = ["nearest", "linear"]; + + + const __wbindgen_enum_GpuFrontFace = ["ccw", "cw"]; + + + const __wbindgen_enum_GpuIndexFormat = ["uint16", "uint32"]; + + + const __wbindgen_enum_GpuLoadOp = ["load", "clear"]; + + + const __wbindgen_enum_GpuMipmapFilterMode = ["nearest", "linear"]; + + + const __wbindgen_enum_GpuPowerPreference = ["low-power", "high-performance"]; + + + const __wbindgen_enum_GpuPrimitiveTopology = ["point-list", "line-list", "line-strip", "triangle-list", "triangle-strip"]; + + + const __wbindgen_enum_GpuSamplerBindingType = ["filtering", "non-filtering", "comparison"]; + + + const __wbindgen_enum_GpuStencilOperation = ["keep", "zero", "replace", "invert", "increment-clamp", "decrement-clamp", "increment-wrap", "decrement-wrap"]; + + + const __wbindgen_enum_GpuStorageTextureAccess = ["write-only", "read-only", "read-write"]; + + + const __wbindgen_enum_GpuStoreOp = ["store", "discard"]; + + + const __wbindgen_enum_GpuTextureAspect = ["all", "stencil-only", "depth-only"]; + + + const __wbindgen_enum_GpuTextureDimension = ["1d", "2d", "3d"]; + + + const __wbindgen_enum_GpuTextureFormat = ["r8unorm", "r8snorm", "r8uint", "r8sint", "r16unorm", "r16snorm", "r16uint", "r16sint", "r16float", "rg8unorm", "rg8snorm", "rg8uint", "rg8sint", "r32uint", "r32sint", "r32float", "rg16unorm", "rg16snorm", "rg16uint", "rg16sint", "rg16float", "rgba8unorm", "rgba8unorm-srgb", "rgba8snorm", "rgba8uint", "rgba8sint", "bgra8unorm", "bgra8unorm-srgb", "rgb9e5ufloat", "rgb10a2uint", "rgb10a2unorm", "rg11b10ufloat", "rg32uint", "rg32sint", "rg32float", "rgba16unorm", "rgba16snorm", "rgba16uint", "rgba16sint", "rgba16float", "rgba32uint", "rgba32sint", "rgba32float", "stencil8", "depth16unorm", "depth24plus", "depth24plus-stencil8", "depth32float", "depth32float-stencil8", "bc1-rgba-unorm", "bc1-rgba-unorm-srgb", "bc2-rgba-unorm", "bc2-rgba-unorm-srgb", "bc3-rgba-unorm", "bc3-rgba-unorm-srgb", "bc4-r-unorm", "bc4-r-snorm", "bc5-rg-unorm", "bc5-rg-snorm", "bc6h-rgb-ufloat", "bc6h-rgb-float", "bc7-rgba-unorm", "bc7-rgba-unorm-srgb", "etc2-rgb8unorm", "etc2-rgb8unorm-srgb", "etc2-rgb8a1unorm", "etc2-rgb8a1unorm-srgb", "etc2-rgba8unorm", "etc2-rgba8unorm-srgb", "eac-r11unorm", "eac-r11snorm", "eac-rg11unorm", "eac-rg11snorm", "astc-4x4-unorm", "astc-4x4-unorm-srgb", "astc-5x4-unorm", "astc-5x4-unorm-srgb", "astc-5x5-unorm", "astc-5x5-unorm-srgb", "astc-6x5-unorm", "astc-6x5-unorm-srgb", "astc-6x6-unorm", "astc-6x6-unorm-srgb", "astc-8x5-unorm", "astc-8x5-unorm-srgb", "astc-8x6-unorm", "astc-8x6-unorm-srgb", "astc-8x8-unorm", "astc-8x8-unorm-srgb", "astc-10x5-unorm", "astc-10x5-unorm-srgb", "astc-10x6-unorm", "astc-10x6-unorm-srgb", "astc-10x8-unorm", "astc-10x8-unorm-srgb", "astc-10x10-unorm", "astc-10x10-unorm-srgb", "astc-12x10-unorm", "astc-12x10-unorm-srgb", "astc-12x12-unorm", "astc-12x12-unorm-srgb"]; + + + const __wbindgen_enum_GpuTextureSampleType = ["float", "unfilterable-float", "depth", "sint", "uint"]; + + + const __wbindgen_enum_GpuTextureViewDimension = ["1d", "2d", "2d-array", "cube", "cube-array", "3d"]; + + + const __wbindgen_enum_GpuVertexFormat = ["uint8", "uint8x2", "uint8x4", "sint8", "sint8x2", "sint8x4", "unorm8", "unorm8x2", "unorm8x4", "snorm8", "snorm8x2", "snorm8x4", "uint16", "uint16x2", "uint16x4", "sint16", "sint16x2", "sint16x4", "unorm16", "unorm16x2", "unorm16x4", "snorm16", "snorm16x2", "snorm16x4", "float16", "float16x2", "float16x4", "float32", "float32x2", "float32x3", "float32x4", "uint32", "uint32x2", "uint32x3", "uint32x4", "sint32", "sint32x2", "sint32x3", "sint32x4", "unorm10-10-10-2", "unorm8x4-bgra"]; + + + const __wbindgen_enum_GpuVertexStepMode = ["vertex", "instance"]; + + + const __wbindgen_enum_HardwareAcceleration = ["no-preference", "prefer-hardware", "prefer-software"]; + + + const __wbindgen_enum_ReadableStreamType = ["bytes"]; + + + const __wbindgen_enum_ReferrerPolicy = ["", "no-referrer", "no-referrer-when-downgrade", "origin", "origin-when-cross-origin", "unsafe-url", "same-origin", "strict-origin", "strict-origin-when-cross-origin"]; + + + const __wbindgen_enum_RequestCache = ["default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached"]; + + + const __wbindgen_enum_RequestCredentials = ["omit", "same-origin", "include"]; + + + const __wbindgen_enum_RequestMode = ["same-origin", "no-cors", "cors", "navigate"]; + + + const __wbindgen_enum_RequestRedirect = ["follow", "error", "manual"]; + + + const __wbindgen_enum_ResizeObserverBoxOptions = ["border-box", "content-box", "device-pixel-content-box"]; + const IntoUnderlyingByteSourceFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_intounderlyingbytesource_free(ptr, 1)); + const IntoUnderlyingSinkFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_intounderlyingsink_free(ptr, 1)); + const IntoUnderlyingSourceFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_intounderlyingsource_free(ptr, 1)); + const WebHandleFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_webhandle_free(ptr, 1)); + + function addToExternrefTable0(obj) { + const idx = wasm.__externref_table_alloc(); + wasm.__wbindgen_externrefs.set(idx, obj); + return idx; + } + + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => { + if (wasm) wasm.__wbindgen_destroy_closure(state.a, state.b); + }); + + function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; + } + + function getArrayF32FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getFloat32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len); + } + + function getArrayI16FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getInt16ArrayMemory0().subarray(ptr / 2, ptr / 2 + len); + } + + function getArrayI32FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getInt32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len); + } + + function getArrayI8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getInt8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); + } + + function getArrayJsValueViewFromWasm0(ptr, len) { + ptr = ptr >>> 0; + const mem = getDataViewMemory0(); + const result = []; + for (let i = ptr; i < ptr + 4 * len; i += 4) { + result.push(wasm.__wbindgen_externrefs.get(mem.getUint32(i, true))); + } + return result; + } + + function getArrayU16FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint16ArrayMemory0().subarray(ptr / 2, ptr / 2 + len); + } + + function getArrayU32FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len); + } + + function getArrayU8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); + } + + let cachedDataViewMemory0 = null; + function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; + } + + let cachedFloat32ArrayMemory0 = null; + function getFloat32ArrayMemory0() { + if (cachedFloat32ArrayMemory0 === null || cachedFloat32ArrayMemory0.byteLength === 0) { + cachedFloat32ArrayMemory0 = new Float32Array(wasm.memory.buffer); + } + return cachedFloat32ArrayMemory0; + } + + let cachedInt16ArrayMemory0 = null; + function getInt16ArrayMemory0() { + if (cachedInt16ArrayMemory0 === null || cachedInt16ArrayMemory0.byteLength === 0) { + cachedInt16ArrayMemory0 = new Int16Array(wasm.memory.buffer); + } + return cachedInt16ArrayMemory0; + } + + let cachedInt32ArrayMemory0 = null; + function getInt32ArrayMemory0() { + if (cachedInt32ArrayMemory0 === null || cachedInt32ArrayMemory0.byteLength === 0) { + cachedInt32ArrayMemory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32ArrayMemory0; + } + + let cachedInt8ArrayMemory0 = null; + function getInt8ArrayMemory0() { + if (cachedInt8ArrayMemory0 === null || cachedInt8ArrayMemory0.byteLength === 0) { + cachedInt8ArrayMemory0 = new Int8Array(wasm.memory.buffer); + } + return cachedInt8ArrayMemory0; + } + + function getStringFromWasm0(ptr, len) { + return decodeText(ptr >>> 0, len); + } + + let cachedUint16ArrayMemory0 = null; + function getUint16ArrayMemory0() { + if (cachedUint16ArrayMemory0 === null || cachedUint16ArrayMemory0.byteLength === 0) { + cachedUint16ArrayMemory0 = new Uint16Array(wasm.memory.buffer); + } + return cachedUint16ArrayMemory0; + } + + let cachedUint32ArrayMemory0 = null; + function getUint32ArrayMemory0() { + if (cachedUint32ArrayMemory0 === null || cachedUint32ArrayMemory0.byteLength === 0) { + cachedUint32ArrayMemory0 = new Uint32Array(wasm.memory.buffer); + } + return cachedUint32ArrayMemory0; + } + + let cachedUint8ArrayMemory0 = null; + function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; + } + + function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + const idx = addToExternrefTable0(e); + wasm.__wbindgen_exn_store(idx); + } + } + + function isLikeNone(x) { + return x === undefined || x === null; + } + + function makeMutClosure(arg0, arg1, f) { + const state = { a: arg0, b: arg1, cnt: 1 }; + const real = (...args) => { + state.cnt++; + const a = state.a; + state.a = 0; + try { + if (!wasm) return; + return f(a, state.b, ...args); + } finally { + state.a = a; + real._wbg_cb_unref(); + } + }; + real._wbg_cb_unref = () => { + if (--state.cnt === 0) { + if (wasm) wasm.__wbindgen_destroy_closure(state.a, state.b); + state.a = 0; + CLOSURE_DTORS.unregister(state); + } + }; + CLOSURE_DTORS.register(real, state, state); + return real; + } + + function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1, 1) >>> 0; + getUint8ArrayMemory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; + } + + function passArrayJsValueToWasm0(array, malloc) { + const ptr = malloc(array.length * 4, 4) >>> 0; + for (let i = 0; i < array.length; i++) { + const add = addToExternrefTable0(array[i]); + getDataViewMemory0().setUint32(ptr + 4 * i, add, true); + } + WASM_VECTOR_LEN = array.length; + return ptr; + } + + function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; + } + + function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_externrefs.get(idx); + wasm.__externref_table_dealloc(idx); + return value; + } + + let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + function decodeText(ptr, len) { + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); + } + + const cachedTextEncoder = new TextEncoder(); + + if (!('encodeInto' in cachedTextEncoder)) { + cachedTextEncoder.encodeInto = function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; + }; + } + + let WASM_VECTOR_LEN = 0; + + let wasmModule, wasmInstance, wasm; + function __wbg_finalize_init(instance, module) { + wasmInstance = instance; + wasm = instance.exports; + wasmModule = module; + cachedDataViewMemory0 = null; + cachedFloat32ArrayMemory0 = null; + cachedInt16ArrayMemory0 = null; + cachedInt32ArrayMemory0 = null; + cachedInt8ArrayMemory0 = null; + cachedUint16ArrayMemory0 = null; + cachedUint32ArrayMemory0 = null; + cachedUint8ArrayMemory0 = null; + wasm.__wbindgen_start(); + return wasm; + } + + async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + } catch (e) { + const validResponse = module.ok && expectedResponseType(module.type); + + if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { throw e; } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + } else { + return instance; + } + } + + function expectedResponseType(type) { + switch (type) { + case 'basic': case 'cors': case 'default': return true; + } + return false; + } + } + + function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (module !== undefined) { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + const instance = new WebAssembly.Instance(module, imports); + return __wbg_finalize_init(instance, module); + } + + async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (module_or_path !== undefined) { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); + } + + + + +function deinit() { + __wbg_init.__wbindgen_wasm_module = null; + wasmModule = null; + wasm = null; + cachedDataViewMemory0 = null; + cachedFloat32ArrayMemory0 = null; + cachedInt16ArrayMemory0 = null; + cachedInt32ArrayMemory0 = null; + cachedInt8ArrayMemory0 = null; + cachedUint16ArrayMemory0 = null; + cachedUint32ArrayMemory0 = null; + cachedUint8ArrayMemory0 = null; +} + +return Object.assign(__wbg_init, { initSync, deinit }, exports); +} diff --git a/apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer_bg.nodedc.wasm b/apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer_bg.nodedc.wasm new file mode 100644 index 0000000..4430a34 --- /dev/null +++ b/apps/control-station/vendor/rerun-web-viewer-0.36.3/re_viewer_bg.nodedc.wasm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b661de545ac90bb950eda1a28fc5a7c3ce4ca7f5bb24894789acf7589495185 +size 50469154 diff --git a/docs/15_LABORATORY_RUN_CANON.md b/docs/15_LABORATORY_RUN_CANON.md index 9da87b5..8963b81 100644 --- a/docs/15_LABORATORY_RUN_CANON.md +++ b/docs/15_LABORATORY_RUN_CANON.md @@ -6,9 +6,11 @@ Status: accepted, 2026-07-26 Mission Core has two different catalogs over related evidence: -- **Data → Sessions and records** contains only original physical captures. +- **Data → Sessions and records** contains independent original physical captures. Examples: `RAVNOVES00`, `TEST007`, `TEST009`. A source record is immutable - evidence received from a device or recording adapter. + evidence received from a device or recording adapter. Per the 2026-09-21 + owner decision, physical passes acquired by the planner are shown with their + studies in **LAB → Planner**, not in Data or the reference selector. - **Test contour → Laboratory contours** contains derived experimental runs. Examples: LAB E24, E25, E26, E28 and E29. A LAB run references an original record and never becomes another original capture. @@ -16,7 +18,10 @@ Mission Core has two different catalogs over related evidence: The backend may keep both entities in one durable SQLite catalog, but every consumer must request an explicit catalog scope: -- `scope=source` for original records; +- `scope=standalone` for Data and planner reference choices: original records + excluding explicit planner-acquisition bindings; +- `scope=source` for all original records, including planner passes needed for + recorded comparisons and scientific inspection; - `scope=laboratory` for LAB projections; - `scope=all` only for internal joins that must resolve both a derivative and its source. @@ -24,6 +29,12 @@ consumer must request an explicit catalog scope: Deleting, renaming or moving source payloads to make the UI look clean is forbidden. Product separation is expressed by typed projections. +A planner pass remains a physical source, not a synthetic LAB projection. +Classification uses exact run/session acquisition bindings, never names or +registration success. Hiding it from Data does not remove its raw evidence or +its historical planning report. See +[the full-reference/catalog audit](audits/2026-09-21-whole-reference-and-capture-catalogs.md). + ## Current reference-source policy RAVNOVES00 is the sole active physical reference source for the current diff --git a/docs/adr/0045-upstream-rerun-canonical-recorded-lab-pipeline.md b/docs/adr/0045-upstream-rerun-canonical-recorded-lab-pipeline.md index 2705394..e5a65d1 100644 --- a/docs/adr/0045-upstream-rerun-canonical-recorded-lab-pipeline.md +++ b/docs/adr/0045-upstream-rerun-canonical-recorded-lab-pipeline.md @@ -3,6 +3,10 @@ Date: 2026-08-30 Status: accepted; RAVNOVES004TREE is the first migrated full-route LAB +Navigation amendment, 2026-09-21: [ADR 0052](0052-native-rerun-grid-navigation.md) +records the owner-authorized, bounded native-camera patch. The historical +decision below remains intact; single renderer/clock/data ownership still applies. + Implementation audit, 2026-09-05: see the [complete customization inventory](../audits/2026-09-05-rerun-customization-inventory.md) and [upstream provenance evidence](../audits/2026-09-05-rerun-upstream-evidence.json). diff --git a/docs/adr/0052-native-rerun-grid-navigation.md b/docs/adr/0052-native-rerun-grid-navigation.md new file mode 100644 index 0000000..1c50cda --- /dev/null +++ b/docs/adr/0052-native-rerun-grid-navigation.md @@ -0,0 +1,75 @@ +# ADR 0052: Native Rerun grid navigation + +Date: 2026-09-21 +Status: implemented and installed; checks and manual acceptance bounds are in the navigation audit + +## Decision and authority + +The owner accepted the restored recorded cloud quality, then explicitly asked +to finish navigation: pan the pivot on the grid, orbit around that fixed pivot, +and zoom independently of the route extent. OPS is updated only after completion. + +This is a bounded amendment to ADR 0045's **unmodified web viewer** requirement. +Rerun SDK, recording format, data, timeline, native renderer and single-viewer +ownership remain unchanged. The web viewer stays on upstream **0.36.3**, with +one reproducible, hash-checked source patch. The archived 0.34.1 fork is not +reactivated. No second camera/renderer, pointer injection or HTTP request per +gesture is introduced. + +## Camera contract + +- The native orbital pivot lies on the native `LineGrid3D` plane (XY in the + product's world frame). Pan translates the whole camera rig along that plane; + orbital rotation leaves the pivot fixed. Scene changes cannot choose a new + pivot after operator navigation. The guide grid is not a measured terrain model. +- Initial fallback height is projected before rendering, not at the start of + the first rotation. Active interpolation stops on manual navigation. +- Zoom changes distance to the pivot. The scene-diagonal zoom-out cap is removed. + Upstream's 0.02 m collision-with-pivot guard and a finite 1e17 arithmetic guard + remain; these are not route-distance limits. Passing through the pivot would + reverse orbital direction, so it is deliberately not implemented. +- Explicit tracking remains distinct from fixed-pivot inspection. While following, + Rerun may move the target with the tracked entity. First-person movement keeps + upstream behavior. Reset and plan/3D switches remain explicit preset actions. +- A read-only native snapshot supplies the last rendered eye to the existing + iframe facade. The approximate DOM-input camera journal is removed. Snapshots + cross the disposable iframe as copied primitive JSON only; no native handle or + listener escapes its lifetime. With no rendered 3D frame, the snapshot is null. +- Display-only blueprint activation carries that actual native eye. Omitting + eye fields resurrects the incoming store's startup camera, as reproduced in + browser QA; stable view identity alone is insufficient. This is one snapshot + per settings update, not a second input controller or HTTP per gesture. +- Mission Core admits one active spatial pane per iframe. The snapshot selects + the last rendered spatial state of the active recording, ignoring retained + states from earlier views. Multiple simultaneous 3D panes would require an + explicit view-id argument before that product composition is admitted. + +## Build, upgrade and rollback + +`vendor/rerun-web-viewer-0.36.3/NODEDC_NAVIGATION.patch` is applied to the pinned +upstream commit. `scripts/build-rerun-navigation.sh` runs in a bounded temporary +Worker006 container, not on the 18 GB operator Mac. It tests native camera math, +uses Rerun's own release/WebAssembly builder and matching JS transformation, +and emits the paired runtime and declarations. No scanner data enters the build. + +`navigation-build.json` binds upstream, source patch, compiler image and artifact +hashes. The installer validates every input before modifying any package file, +and rejects an unfamiliar SDK version or wrapper. Fresh installs and production +builds use that same installer once the candidate is accepted. + +An upgrade requires rebasing this patch, native tests, JS/WASM ABI checks, +frontend contracts and real-browser navigation/regression QA. Do not copy the +old WASM into a newer SDK. To roll back, disable the installer hooks and restore +the exact official 0.36.3 package; the facade treats a missing native snapshot as +unavailable, never fabricates an operator eye. Raw/corrected recordings do not +need to be regenerated. + +## Acceptance + +Required: plane/pan/orbit/zoom invariants, different grid plane, top-down and +first-person regression; exact native snapshot and realm cleanup; normal and +expanded product views, Escape, explicit reset, layers/point-size updates and +follow transitions. A successful compile alone is not product acceptance. + +The accepted full-fidelity cloud and 30-minute accumulation are outside this +navigation patch and must not be reduced to make testing cheaper. diff --git a/docs/audits/2026-09-21-corrected-session-default.md b/docs/audits/2026-09-21-corrected-session-default.md new file mode 100644 index 0000000..ebc1e3f --- /dev/null +++ b/docs/audits/2026-09-21-corrected-session-default.md @@ -0,0 +1,106 @@ +# Corrected session as the operator and laboratory default + +## Decision and product surface + +Owner direction supersedes the earlier *view-only* admission in +`2026-09-21-map-comparison-view.md`: an explicitly reviewed correction becomes +the ordinary representation of the same physical session in Data, and the +default reference when that recording is selected for a new planner study. +Original evidence stays immutable and available in Information → comparison. + +This is domain content in an admitted composition (class A), not a new catalog +row, root, workspace, solver or viewer. Rejected alternatives: duplicating the +capture under another name, and connecting only the bounded preview while the +planner still consumes original geometry. Canonical SegmentedControl, +RangeControl and LoadingRegion remain the only UI primitives used here. + +States: no activated correction → original; activated correction → corrected; +missing/corrupt selected correction → explicit error, never silent original +fallback. Comparison is an optional inspector control, not a different default +policy. Initial overview geometry remains hidden until the selected +representation is applied. Version and clipping changes retain the viewer and +camera; only explicit camera controls reset the view. + +## Architecture and source ownership + +`SessionMapVersions` stores immutable complete bundles in application data and +atomically selects one generation for each physical session. Activation is an +explicit offline operation after review, not automatic acceptance of any +solver output. `activate_session_map.py` verifies the original transport, +index, frozen clock and origin before admission. The pointer grants only +recorded playback and laboratory reference use. It cannot authorize movement. + +Data uses `MapReplaySessionStore`, a read-only playback facade over the real +catalog. `ReplayCommand.map_version` pins the geometry generation before work +is queued. Native capture confinement remains unchanged; three separately +verified projection inputs (manifest, complete points, trajectory) extend the +preparation/cache identity. A raw ready-job cannot satisfy a corrected launch. +The existing single background worker and seekable RRD path are reused. + +The K1 exporter reads the exact corrected cloud frame and corrected pose, +including orientation, at the corresponding original receipt time. The raw +capture clock, duration, media and event sequence are unchanged. Map timestamps +are relative to the first raw message, not the first pose or capture start. +All cloud/pose ownership is checked before publication. The normal operator +RRD's existing temporal display sampling is retained; no 180k-point preview is +substituted for playback or registration data. Point-color overlays use the +same corrected positions and preserve observation count/order. Uncorrected +perception overlays are rejected instead of mixing two spatial frames. + +New planner selections resolve through `DefaultPlanningSources.get`, retaining +the physical session's name. Existing studies resolve their pinned generation +through `bound`/`verify`: old studies retain original geometry, and corrected +studies use their immutable map even if the default later changes. The full +map and trajectory feed the existing reference extraction/preparation. No +registration acceptance thresholds or search rules were weakened. + +Planner preview requests also carry the reference generation. An existing +original study must not display a newly corrected default as its old reference. +Information retains both representations and original acquisition metrics. + +## Scope and verification + +Original capture bytes and the previously reviewed correction/solver seal are +unchanged. The correction remains a regularized estimate validated against +this capture, not independent proof of positioning accuracy. Admission is +for the next supervised scanner experiment, not rover movement. + +### Acceptance completed + +- Frontend architecture checks, typecheck, all 885 frontend tests, and the + production build passed sequentially. Focused Python suites passed: the + 162-test integration selection, the 70-test overview/API/live-planning + selection, and the final 31-test recording/exporter-capability selection + (these selections overlap; their counts are not additive). +- Synthetic coverage verifies durable activation, historical generation pins, + fail-closed missing/corrupt defaults, native path confinement, source digest + ownership, corrected frame/pose projection, and unchanged replay timing. +- The reviewed full correction was explicitly activated in application data; + no physical session was duplicated, and no original capture was modified. + Canonical source lookup now returns the same selected map generation as + recorded playback and the Information comparison. +- Actual operator RRD inspection matched all 1,036 displayed cloud frames to + their complete-map counterparts and all 5,182 poses/orientations to the + corrected trajectory. Original timing (597.439503125 seconds) and media + timing remain unchanged. The ordinary operator display sampling is retained. +- Isolated full-route planner preparation used the selected corrected source, + produced 15 reference tiles and 694,420 voxel points, and verified all 5,182 + trajectory poses across approximately 579.98 metres. It did not persist a + real study, issue device commands, or grant vehicle-control authority. +- Browser acceptance on canonical port 8000: ordinary recording-name selection + opens playback; Information initially selects Corrected; Original/Corrected + switching works; window restore/expand works and Escape closes Information. + A new planner selection of the same ordinary name displays Corrected and + 579.98 metres. Selecting Whole trajectory includes all 5,182 positions. + No browser console errors were reported during this acceptance. +- One canonical backend remains listening on 8000; no backend listens on 8765. + +### Next supervised experiment + +Use a **new** planner study, the ordinary physical recording name, and **Whole +trajectory** so the reference includes both sides of the closure. Existing +studies deliberately retain their pinned generation. Try several stationary +initializations near the seam, including offset positions, then short walks +through it after tracking is confirmed. Inspect those runs before extending to +kilometre-scale routes. This admission is not an independent measurement of +localization accuracy or autonomous-driving readiness. diff --git a/docs/audits/2026-09-21-full-route-search-fix.md b/docs/audits/2026-09-21-full-route-search-fix.md new file mode 100644 index 0000000..d007efc --- /dev/null +++ b/docs/audits/2026-09-21-full-route-search-fix.md @@ -0,0 +1,118 @@ +# Complete route search before provisional localization + +## Decision and scope + +Owner-approved follow-up to the [entry/seam audit](2026-09-21-ring-entry-and-seam-review.md). +Fix cold-start search scheduling, retain the precise start-area solver and all +geometric/fresh-data gates. No device commands, threshold relaxation, map +correction changes, endpoint snapping or retroactive edit of the failed study. + +`route-relocalization/v7` compares the complete selected route before choosing a +provisional place. A finite hypothesis queue replaces the shared 35-second +deadline. `stationary-fresh-bootstrap/v4` separates search geometry from current +localization evidence; an old prefix can never establish tracking directly. + +## Algorithm and boundaries + +1. Collect the existing 10-second stationary prefix. +2. Run all 108 established dense-start seeds at the start/last confirmed hint. + Keep a qualified dense candidate, but do not accept it before route comparison. + A missing local start target does not prohibit searching the remaining map. +3. Index the complete reference and rank all geometrically usable route anchors, + spaced by the existing 5-m policy. Ranking changes order, never eligibility. +4. At each anchor, test the existing three yaw proposals with two translations: + sensor entry to route anchor, and the former cloud-median placement. One + prepared local target tree is shared by all six fits. No atlas density cap + or new thinning is introduced. +5. Compare the resulting SE(3) clusters together with the dense-start candidate. + A distinct similarly good place remains ambiguous; first passing fit is not + accepted. A failed fresh trial can advance to the next unambiguous candidate, + with a new receipt fence and no reused validation data. +6. During search, continue checking source identity/order, receipt segment, + pose/cloud freshness and stationary translation (existing 0.10-m envelope). + Movement or broken input invalidates the hypothesis. The existing 40-second + prefix-age fence remains for the legacy local protocol, not for v7's explicitly + monitored stationary whole-route search. +7. After the result arrives, discard all pre-result receipts for confirmation. + Three consistent fresh geometric checks remain mandatory before tracking. + Ordinary tracking, local reference extraction, quality thresholds and the + 8-second fresh-result gate are unchanged. + +The numerical child emits progress after individual work units. The supervisor +allows arbitrary total search duration while work advances; 60 seconds with no +progress is a **worker stall**, not evidence that the location is unknown. +STOP, source end, moved-search cancellation and teardown terminate/reap that +exact child. A queued operator retry waits for its release and starts with a new +prefix. Recording remains owned by the device plugin. + +The candidate queue stays in one worker across scheduling batches; this is not +a durable checkpoint/resume facility after a process crash. A failed worker +returns incomplete search and offers reinitialization without stopping recording. +Operator copy uses the existing status surface under the product UI canon. + +## Real saved-input regressions + +Same immutable run `473870e0-5210-452c-b9e4-9d7937e93646`, query +`20260921T103511Z_viewer_live`, corrected full reference +`JA-STROITEL-SUN-RING · correction v2`, 579.98 m / 694,420 points. +Only original initialization arrays enter the production worker. No end pose, +later successful transform, manually selected anchor or offline deadline override. +One CPU worker at a time; numerical thread counts fixed to one. + +| Original attempt | New result | Surface overlap | Inlier RMSE | Worker wall | +| --- | --- | --- | --- | --- | +| First, failed cold start near route end | Candidate at anchor 113 / 565 m | 98.8467% | 0.14330 m | 138.58 s | +| Second, familiar start | Candidate at start | 98.7191% | 0.13466 m | 156.52 s | + +Both searches completed 810/810 fits: 108 dense + 117 anchors × 3 yaws × 2 seeds. +No remaining route anchors; no competing qualified location close enough to +trigger ambiguity. The second case retained the dense-start solution rather +than degrading its target geometry. + +All 159 files of the original saved study were hashed before/after each replay +and remained unchanged. Raw evidence and generated arrays/results remain in the +ignored private `2026-09-21-full-route-fix` audit directory. The earlier study +still truthfully records its failed first initialization. + +## Validation and review + +- 121 focused backend tests passed: route coverage, last-ranked candidate, + dense/global competition, median-seed regression, anchor-origin placement, + multi-minute search, stationary continuity, distinct fresh windows, stale and + ambiguous rejection, live recovery, cancellation, same-recording retry, + child stall/forced cleanup and corrected-map source admission. +- Focused Ruff and `git diff --check` passed. +- Replacing median placement outright failed the existing lateral-offset GICP + regression; therefore both initializations are retained. No quality threshold + was weakened to make that test pass. +- Registration/correction producer files and reference generations are unchanged. +- Application architecture checks, TypeScript, all 887 frontend tests and the + production build passed sequentially. Only the existing large-chunk build + warning remains. Normal/expanded planner, latest ring evidence and Escape menu + dismissal were checked in the existing in-app browser; no live state was faked. +- With acquisition idle and the old study completed, the canonical LaunchAgent + was restarted. Health/liveness passed on the sole port-8000 backend (PID 64086); + no backend on 8765 and no temporary search/test workers remain. New captures + use v7; the old report continues to expose its original v6 evidence. + +## Limitations and next field acceptance + +Completeness is relative to the declared finite anchor/yaw/seed policy, not a +mathematical proof that every pose in continuous space was searched. Descriptor +ranking remains imperfect; queue completeness prevents a low rank from excluding +the real place, but is not a fast kilometer-scale place-recognition index. + +Full comparison increases even familiar-start latency to roughly 2–3 minutes on +this 580-m map. This is an explicit regression in speed, accepted here for complete +comparison, not a claim of final optimization. Target preprocessing is reused; +safe coarse-to-fine acceleration still needs independent evidence. + +Offline replay proves the corrected numerical cold-start outcome, **not** live +stationary continuity or independent absolute pose accuracy. Synthetic tests +prove the temporal contract; the operator must confirm it on a new capture. +Surface overlap/RMSE are not position accuracy or autonomous-driving acceptance. + +Field check: start at the formerly rejected place (last point of the old pass), +leave the scanner stationary through search and fresh confirmation, then walk +20–30 m across the seam and back. Record the time to tracking and any interruption. +No longer route is needed to answer this regression question. diff --git a/docs/audits/2026-09-21-large-ring-offline-qualification.md b/docs/audits/2026-09-21-large-ring-offline-qualification.md new file mode 100644 index 0000000..e9e3d4c --- /dev/null +++ b/docs/audits/2026-09-21-large-ring-offline-qualification.md @@ -0,0 +1,244 @@ +# Large-ring offline qualification — 21 September 2026 + +## Scope and retained authority + +The owner supplied `JA-STROITEL-SUN-RING-002`, session +`20260921T134309Z_viewer_live`, and requested source, drift, correction and +offline-localization testing without another physical capture. The finish was +approximately 3–5 m beyond the physical start. Endpoint equality is therefore +specifically prohibited as a fitting constraint. + +All geometry, detailed results, scripts and SHA-256 inventories are private under +`.runtime/audits/2026-09-21-large-ring-002/`. No hardware command, source overwrite, +map activation, planner mutation, production-code edit, service restart or motor +authority is part of this audit. Earlier unrelated uncommitted work is retained. + +## Source acceptance + +- Saved capture: 1,446.684 s; catalog and RRD preparation are ready. +- Spatial trajectory: 1,595.409 m over 1,362.429 s of pose receipts. +- 13,608 cloud frames, 13,624 poses, 49,217,854 decoded points. +- All 31,450 metadata records cover the complete 573,728,851-byte transport. +- Every payload SHA-256, topic, length and frame offset was checked against + metadata: 31,450/31,450 agree, zero mismatches. +- Transport SHA-256 agrees with its capture manifest. Cloud/pose sequences have + no missing numbers or resets; decoded geometry and receipt order are valid. +- No capture reconnect, rejected transport message or recovery gap. Maximum + cloud receipt gap is 1.344 s; maximum pose receipt gap is 1.189 s. +- No pose discontinuity under the existing live criterion; largest successive + position step is 0.1515 m. Single-receipt speed is not physical velocity: + transport bursts compress receipt intervals. Five-second travel averages + reach 1.501 m/s. Hardware synchronization is not established by these checks. +- Nearest pose-to-cloud receipt gap: p95 42.7 ms, maximum 180.8 ms. These are + host-clock observations, not per-point LiDAR firing-time reconstruction. + +The raw endpoint displacement is 4.000 m horizontally and +1.5875 m vertically, +4.3034 m in 3D. This includes real operator displacement and is NOT a surveyed +drift measurement. The corrected result must retain the real overshoot. + +The control-plane acquisition ended with +`acquisition.stop.accepted_physical_outcome_unknown`, cleanup pending false. +This is a separate physical-stop confirmation issue. The receiver reports +`external_stop`, no transport error, and the saved recording is ready; do not +misreport that control state as corruption of the completed cloud. + +## Existing recipe failure and diagnostic variant + +The unchanged `recorded-ring-experiment/v1` recipe stopped before producing a +correction. Its first-20-s/last-5-s fit converged: overlap 95.317%, inlier RMSE +0.1641 m, correction at the patch center 4.2176 m / 2.6199 degrees. Rejection was +solely the ordinary local-tracking correction guard of 3 m. + +Merely changing that guard is insufficient. Four disjoint five-second tail +probes using the existing coarse acquisition policy failed the unchanged +bidirectional-consistency gate. The first fit's cycle was 0.0280 m / 0.6007 +degrees, against the existing 0.1 m / 0.2-degree limits. + +A declared training-only grid of first windows [10, 20, 40] s and last windows +[10, 20, 30] s was evaluated without using withheld frames. Two of nine passed +the existing quality and cycle gates: 20/30 and 40/30. The larger 40/30 support +was selected before held-out evaluation. It yields 72.777% overlap, 0.1875 m +inlier RMSE and a 0.0194 m / 0.0973-degree bidirectional cycle. Its lower overlap +reflects a different, larger point population; it is not comparable to the +short-window 95% as an identical-denominator score. + +This private adapter uses the already defined route-acquisition registration +policy for the first coarse seam fit only. Ordinary local registration, +shape/information gates, overlap/RMSE thresholds, reverse consistency, solver and +validation policy remain unchanged. Production modules were not patched. + +All 80 attempted neighboring-window links failed overlap and RMSE gates; ten +also failed convergence. None was admitted. The reconstruction is therefore +one measured closure plus a smoothness prior, NOT recovery of a densely +measured drift history. The input is K1 mapped increments, not native LiDAR +sweeps from which a new independent SLAM solution was reconstructed. + +## Frozen candidate and held-out evaluation + +The existing `smooth-map-correction-experiment/v2` spline solver and full-frame +materializer produced a separate experimental candidate. Every ten seconds, +the two-second phase 4–6 interval was withheld: 2,855 frames, with 10,753 retained +for fitting. Shared upstream K1 mapping means this split is not independent +survey truth, despite disjoint frame membership. + +| Measurement | Original | Diagnostic corrected candidate | +| --- | ---: | ---: | +| Local held-out windows accepted | 134/136 | 136/136 | +| Median local overlap, 0.5 m radius | 98.077% | 98.081% | +| Median local inlier RMSE | 0.16495 m | 0.16563 m | +| Cross-visit held-out overlap | 22.723% | 95.093% | +| Cross-visit median all-point distance | 1.2018 m | 0.1186 m | +| Cross-visit p95 all-point distance | 2.5941 m | 0.4914 m | +| Endpoint XY separation | 4.000 m | 2.801 m | +| Endpoint Z difference | +1.5875 m | −0.0112 m | +| Trajectory length | 1,595.409 m | 1,599.807 m | + +Cross-visit evaluation fixes the same 7,499 source points in 18 held-out tail +frames, compares them only to retained first-20-s geometry, and performs no +additional fit. Those query frames did not enter closure selection or fitting. +The source and corrected failed-window checks use the same frozen priors; +fresh one-second halves in original failure groups 74 and 120 all qualify. + +The remaining 2.8 m endpoint separation is compatible with, but does not survey, +the operator's 3–5 m overshoot. Near-zero endpoint Z is not centimetre absolute +accuracy. A fit between coincident-looking endpoints was never imposed. + +Maximum trajectory displacement is 8.164 m, maximum displacement gradient +0.04123 m per travelled metre (p95 0.03768). Changing regularizer strength by +0.5×/2× changes trajectory positions by at most 0.009 mm; this is numerical +prior stability, NOT proof of the actual spatial distribution of drift. +Using the other qualifying 20/30-second closure measurement instead changes +the corrected trajectory by up to 0.2483 m (p95 0.2262 m). This measurement-window +sensitivity is materially larger than regularizer sensitivity and is retained +as model uncertainty, not hidden by the visually closed seam. + +All 49,217,854 materialized points and all 13,624 corrected poses were reproduced +from the frozen field. Within-frame motion is rigid. Maximum coordinate +serialization error is 0.0306 mm; sampled intra-frame pair-distance error is +0.0601 mm. No count, intensity, frame order or raw source was discarded. + +## Independent archived passes and complete-route search + +The production versioned-map extractor and tiled reference builder were reused +through a private adapter, not through an admitted catalog generation. The +whole route yields 1,924,498 original and 1,940,166 corrected registration +points. Preparation took 25.48 / 26.67 s. No 30 m crop or whole-map point-count +cap was introduced. Presentation's 80 m envelope is not the local numerical +matching profile. + +The first independent query is the original cold prefix from study +`473870e0-5210-452c-b9e4-9d7937e93646`, captured in a different session. Only its +original query points and sensor origin are loaded, not its old reference or +accepted transform. Full production v7 search tests all 2,034 fits: 108 dense +start hypotheses and 1,926 route hypotheses. + +| Same independent cold input | Original atlas | Corrected atlas | +| --- | ---: | ---: | +| Complete search | 2,034/2,034 | 2,034/2,034 | +| Result | Candidate | Candidate | +| Time | 320.41 s | 317.07 s | +| Overlap | 98.899% | 98.978% | +| Inlier RMSE | 0.1493 m | 0.1449 m | + +This does not demonstrate a material speedup or inability to localize against +the original. Correction's demonstrated benefit is cross-visit map agreement. + +The corrected atlas then accepts all 29 stored fresh snapshots over the +101.633 m independent walk using a causal previous-accepted-transform chain. +Overlap is 98.546–99.823%, inlier RMSE 0.1181–0.1501 m; measured local calculation +time is 0.073–0.105 s. Real recorded request/receipt clocks are retained; measured +rerun calculation duration is substituted. This is an archived-snapshot replay, +NOT a live ingress, camera, scheduler, motion-stationarity or recovery test. + +The original atlas also accepts all 29 snapshots of that short pass. Thus this +test proves compatibility of the corrected version, not a general superiority +claim for every metric. + +A second independent cold prefix from study +`0324337e-b590-4561-b19a-8fbc7835b888` also qualifies after all 2,034 fits: +352.94 s, 99.108% overlap, 0.1438 m inlier RMSE. Replaying its entire old smaller +ring against the new larger one is **not all-positive**: 30/101 snapshots qualify, +with first rejection at 132.265 m. Reference-path proximity under the last +accepted transform grows from 12.2 m at the last positive window to 18.2 m at +first rejection, then approximately 72 m. This supports a different-route / +out-of-localization-coverage explanation, not a claim that the two rings cover +the same corridor. It is not ground truth during the lost interval. + +Near the old pass's return to the shared area, overlap again reaches 99.25–99.64% +and inlier RMSE about 0.139 m, but correcting the obsolete local prior requires +3.15–3.20 m and is correctly rejected by ordinary tracking policy. This replay +deliberately has no complete recovery/bootstrap state machine. It neither proves +nor disproves physical reinitialization after returning; a fresh stationary +relocalization is a separate operation, not continued green tracking on the old +hint. The original and failed cases are retained in full. + +The middle-route probe selects held-out time group 68 without looking for a +favorable fit: source progress 794.812 m, 11,625 query points. All 2,855 held-out +frames are excluded from the atlas; query coordinates are translated to a new +origin and rotated 90 degrees. The generating correction is used only to +measure post-fit model consistency, never as the search seed. + +Complete search qualifies after 2,034/2,034 fits in 452.16 s: overlap 98.545%, +inlier RMSE 0.1625 m, selected retrieval anchor at 800 m. Fitted sensor position +is 0.0461 m from the generating model, NOT surveyed truth. This is a same-source +numeric place-retrieval test with shared upstream K1 mapping. The source query +spans 3.804 m of motion; it is explicitly not a valid stationary bootstrap +prefix and must not be presented as live cold-start acceptance at the midpoint. + +In total, four complete cold numerical searches performed 8,136 fits. Runtime +on this Mac ranges from 5.28 to 7.54 minutes. These are measured functional +experiments, not a stress test or a guarantee for larger routes or the onboard +computer. The full finite queue and ambiguity checks remain intact. + +## Negative controls and software regressions + +On real query geometry, local initial-X perturbations 0/1 m converge; 3 m is +rejected by the unchanged correction guard; 5/10/20 m fail numeric-quality +criteria and 1,000 m has no target coverage. These are software initial-guess +perturbations, NOT physical scanner-displacement acceptance. Full-route search +is a different operation and is not restricted by those local outcomes. + +The fixed halfway wrong-region comparison is rejected on surface RMSE. +Applying real positive geometry with future, nine-second-old or prior-segment +receipt metadata is rejected in all three cases. Zero false confirmations in +this small set is not a false-acceptance-rate estimate. + +All 178 focused software regressions pass: correction, registration, full-route +coverage/ambiguity/worker ownership, bootstrap freshness, recovery, replay, +live lifetime/multiple traversals, reference preparation and map-version/default +boundaries. A pre-existing Starlette/httpx deprecation warning remains. These +fixtures do not constitute physical multi-lap or onboard-computer acceptance. + +## Decision boundary + +The data support an experimental correction of this larger loop without +forcing the real endpoints together. The existing automatic recipe is NOT +qualified unchanged: seam acquisition and window-support selection require a +separate production change. Do not lower tracking-quality gates to conceal +that distinction. + +Not proven: independent repeat accuracy throughout the newly added territory, +absolute map/pose error, correct internal drift distribution, arbitrary-loop +discovery, seasonal transfer, simultaneous live latency, target-board budget, +physical multiple laps, navigation safety or motor authority. No further live +scanner trial is needed to reproduce the present offline evidence. + +The next production increment should separate offline closure acquisition from +local tracking correction, qualify window-support selection and preserve the +measured quality/cycle/holdout gates. Do not ship the experiment by silently +changing the global live 3 m guard or forcing final coordinates onto the start. +Only after that separate change and versioned admission should the candidate +be offered as the recording's corrected default. The current default is not +changed by this audit. + +## Runtime and final evidence seal + +Heavy checks ran sequentially with a single numerical worker on the operator +Mac; no stress workload or second application server was introduced. The final +seal verifies 311 input files unchanged and inventories 62 private artifacts. +The only non-ignored repository addition from this audit is this report. +Production modules, original experiment scripts and raw capture remain intact. + +The canonical service remains the same PID 73593 on port 8000. Final health is +operational with zero consecutive reconciler failures; no temporary audit/search +worker or port-8765 listener remains. No restart or hardware action was taken. diff --git a/docs/audits/2026-09-21-map-comparison-view.md b/docs/audits/2026-09-21-map-comparison-view.md new file mode 100644 index 0000000..c6e6cd3 --- /dev/null +++ b/docs/audits/2026-09-21-map-comparison-view.md @@ -0,0 +1,92 @@ +# Corrected map comparison — view-only admission + +## Product surface brief + +Operator job: inspect the recorded ring before and after reviewed correction, +with identical observations, colors, clipping and camera. Primary entity remains +the physical recording; correction is a separate derived representation. + +Placement: Data → Sessions and recordings → selected source → Information. +Extend the admitted overview with a canonical `SegmentedControl` labelled +“Версия облака”: “Исходное / Исправленное”. This is domain content inside an +existing composition (class A), not a new workspace, LAB or product root. +Rejected: adding another catalog session (would impersonate a physical capture), +or a separate experiment viewer (duplicates camera/lifecycle controls). +The planner's source preview does not enable comparison and remains original. + +States: no admitted pair → ordinary original overview; available pair → explicit +version choice; pending update → retain previous view with an updating label; +failure → retain previous view with visible error, never silently label original +geometry as corrected. Source changes invalidate access. The information panel +explicitly refers to original capture metrics in both modes. + +## Identity, geometry and authority + +`overview_comparison.py` publishes a content-addressed, bounded paired geometry +bundle outside the raw session catalog. Its manifest binds original session ID, +overview generation, raw artifact digests and reviewed map generation. An atomic +pointer selects an explicitly published pair; requests pin that pair, rather than +following subsequent publications. No HTTP filesystem path input or mutation API. + +The offline producer verifies the admitted map, its packaging receipt, frozen +decoded-cache seal, original transport/clocks, frame/pose ownership and current +overview. It then samples identical point and pose indices in both geometries. +The preview policy is at most 180,000 points and 20,000 poses, uniformly across +the complete capture including endpoints. These are viewer resource budgets, +not route-distance, acquisition, correction or localization limits. Full +resolution corrected and original files remain unchanged. The preview is not a +ground-truth accuracy measurement or a new live reference. + +## Rerun lifetime + +One original RRD supplies stable application/recording identity. Updates replace +only `/world/cloud`, `/world/route` and `/world/endpoints` through the existing +channel. No new recording, viewer mount, blueprint or camera journal update on +version or clip change. Explicit Top/3D commands may change the camera; both +representations use common bounds. Height controls start unclipped, with maximum +at least 80 m. Paired observation colors are computed once conceptually from +original heights, so a color shift cannot masquerade as geometry correction. + +No Rerun fork customization, solver modification, source-session mutation, +reference promotion, vehicle authority, or frontend root change is introduced. + +## Acceptance + +- Python: 78 focused tests passed across overview/comparison, candidate admission, + packaging, reference preparation, planner, correction and registration. The + 17 overview/comparison tests were rerun after formatting and passed. Coverage + includes exact source/generation binding, hash corruption, symlinks, path + traversal, missing version (409 rather than raw fallback), shared RRD identity, + route/endpoints replacement, unchanged colors, reversible clipping, and no + camera blueprint on representation changes. +- Frontend: architecture gate, full typecheck, all 884 unit tests and production + build passed. After the final footer-only adjustment, architecture + both + overview test files (9 tests), typecheck and production build passed again. +- Ruff and `git diff --check` passed. Existing Vite large-chunk and Starlette + httpx deprecation warnings remain; no dependency change was made for them. +- The real recorded ring was admitted as a view-only pair: 180,000 identical + point indices, all 5,182 poses, original 579.797271 m and corrected 579.982703 m. + Full-resolution original and corrected clouds remain intact. Private manifest + and generation identities live under runtime evidence, not the session catalog. +- Canonical API accepted the pair after restart. IAB visually displayed original + and corrected versions, Top/3D, and height clipping at 5 m then full 80 m. + A manually rotated view remained stable on toggling. Normal/expanded workspace + modes preserved version and clipping; Escape closed the information view. +- Final-build viewport geometry was identical before/after comparison switching: + 308.828125 × 311.78125 CSS pixels at the same coordinates on the narrow IAB. + The footer uses common copy to avoid changing viewer height with the version. + Desktop 1440 × 1000 QA override was reset. The corrected real case remains + open in the canonical IAB; no alternative server or extra viewer was started. +- Resource gate: memory free percentage 40–49%; builds, tests, materialization + and browser work ran sequentially. Docker Desktop was not started. +- Ops reporting remains pending: direct `tasker_get_agent_instructions` and + `tasker_list_projects` both timed out after 60 seconds. No card ID was guessed, + no duplicate card or substitute API write was used. This report preserves the + implementation/acceptance record for the existing card when Ops is reachable. + +## Decision and remaining boundary + +The candidate can now be inspected in the product. That does not admit it for +live localization or autonomous movement. No new scan is necessary just to +compare the two representations. A corrected-reference replay and independent +repeat capture remain separate acceptance stages, not proved by this viewer. diff --git a/docs/audits/2026-09-21-map-reference-version-admission.md b/docs/audits/2026-09-21-map-reference-version-admission.md new file mode 100644 index 0000000..1baabc6 --- /dev/null +++ b/docs/audits/2026-09-21-map-reference-version-admission.md @@ -0,0 +1,117 @@ +# Explicit map-reference version admission + +## Outcome and boundary + +The reviewed smooth ring correction now has a separately sealed, content-addressed +map bundle and an **offline** adapter to the existing planning-source interface. +The whole corrected route was assembled by the unchanged `build_reference_map` +consumer. This is data-path admission, not independent localization acceptance. + +The adapter is deliberately not installed in the web composition. No source +session, active reference, stored draft, live run, matcher policy or viewer was +changed. The ordinary `get(session_id)` still returns the original source; a +candidate requires its exact derivative generation. Nothing chooses “latest”. + +The next integration must add an explicit version choice and matching corrected +preview in the existing planner. Selecting a corrected trajectory while showing +the raw session's overview would be a contract error. Live field instructions +are deferred until that coherent selection/preview path is verified. + +## Implementation and provenance + +- `reconstruction/map_version.py` owns `missioncore.map-reference-version/v1`, + full artifact hashing, exclusive staging, idempotent publication and projections. + It has no SciPy, device-plugin, session-store, viewer or hardware dependency. +- `missions/versioned_sources.py` composes the original provider with one explicitly + pinned candidate. A source generation/digest mismatch or artifact corruption + fails closed; it never falls back to different geometry under the same identity. +- `experiments/package_recorded_map_version.py` is the bounded K1 experiment + adapter. It reads the canonical source API and checks native transport, receipt + index, frozen capture clock and origin. It never imports or writes the web app. +- A reviewed evidence-seal SHA binds the original experiment code, correction, + geometry and measured reports. Packaging adds its own producer/contract hashes + and frame-reproduction receipt without rewriting those reports. + +The bundle preserves full-resolution corrected XYZ, unchanged cached intensities, +pose orientations, original frame offsets/counts/sequences and receipt chronology. +Its manifest binds the original physical session, source generation and every +source-artifact digest. Separate map frame identity prevents confusing corrected +coordinates with the raw scanner frame. + +The trajectory contract explicitly distinguishes `source_distance_m` (the smooth +field's original traversal parameter) from recomputed corrected `distance_m`. +Drafts and route tiling consume the latter. No first/last pose equality is added. + +Multi-tile preparation uses one verified private bundle snapshot and checks the +original and derivative again before the complete map may be returned. Failure, +cancellation and exceptions clean only that temporary snapshot. Existing source +evidence and earlier versions remain untouched. + +## Measured execution + +On the existing ring, all **5,178 full-resolution cloud frames / 17,669,672 points** +were reproduced from the frozen correction field and compared bit-for-bit after +float32 serialization. All **5,182 corrected positions and orientations** were +checked, as were source pose indices, distances and receipt times. + +The canonical source generation remained unchanged. The original trajectory is +579.797271 m; the corrected trajectory is 579.982703 m. The existing reference-map +builder assembled 15 route tiles and 694,420 voxel-retained registration points. +The packaging plus full-route assembly check took 10.36 seconds on the local CPU. +This is one measured execution, not a throughput benchmark or a scalability claim. + +The source/model fidelity and held-out registration findings remain those in +`2026-09-21-smooth-map-correction.md`; packaging did not refit or remeasure them. +The full candidate map includes all source frames. Its assembly therefore must +not be reported as an additional held-out localization score. + +Extraction has a declared, separate numerical profile (120 uniformly selected +frames per interval, 0.25 m voxel, 20 m radius and relative height −3…6 m), matching +the current recorded reference extraction settings. Presentation uses an 80 m +radius and no vertical crop. These are derived preparation profiles, not raw +capture limits, and the full stored bundle is not thinned. Convergence, +loss/recovery, quality thresholds and acquisition remain unchanged. + +## Validation and limitations + +Tests cover idempotency, separate source/version generations and draft geometry, +corrected distance calculation, raw default selection, source/frame ownership, +no second cloud transform, presentation/numerical separation, every artifact's +hash, wrong parent, unsafe paths/symlinks, nonfinite data, bad frame indices, +invalid quaternions, cancellation, exception cleanup and changes during assembly. + +The focused suite passed **61 tests**, including the existing source preparation, +draft and registration regressions and the correction solver tests. Ruff and +diff whitespace checks passed. Frontend code/build and web composition were not +changed or restarted for this offline increment. + +The packaging adapter initially rejected the real source because its provisional +mapping swapped capture-clock and receipt-index artifact names. Inspection of +the plugin's actual source contract corrected that mapping; a dedicated regression +now checks the frozen capture clock separately from the mutable current timeline. +No recorded bytes or source metadata were changed to make admission pass. + +The original correction still has only one accepted closure and a smoothness +prior. Same-source held-out tests do not establish independent positioning truth, +and numerical GICP sensitivity remains an open issue. No new field acceptance, +automatic arbitrary-loop discovery, online SLAM replacement or navigation/safety +authority is claimed. + +## Ops and local runtime + +Ops access instructions, granted projects and Mission Core project context respond. +Card discovery did not recover: a full list and searches for `planning` and `кольц` +each timed out after 60 seconds. The earlier `совмещение` search returned no rows. +No issue was guessed, duplicated or overwritten, and no raw-API fallback was used. +The structured engineering update remains pending card access. + +Local memory was 39% free before the sequential checks; no Docker workload or +parallel heavy validation ran. The canonical operator service on 8000 was retained. +The package and detailed private receipt remain under ignored `.runtime/analysis`. +# Subsequent view-only admission + +The same reviewed candidate is now available for explicit Original/Corrected +comparison in the existing session overview. See +`2026-09-21-map-comparison-view.md` for the separate paired-preview contract, +source identity checks, UI placement and acceptance. This does not change the +reference-promotion or independent-validation boundaries documented below. diff --git a/docs/audits/2026-09-21-native-rerun-navigation.md b/docs/audits/2026-09-21-native-rerun-navigation.md new file mode 100644 index 0000000..b4de97a --- /dev/null +++ b/docs/audits/2026-09-21-native-rerun-navigation.md @@ -0,0 +1,96 @@ +# Native Rerun navigation · 2026-09-21 + +## Scope + +The owner accepted the recorded cloud quality and asked to finish navigation. +No scanner command, recording rewrite, cloud decimation, color change or matching +algorithm change belongs to this increment. OPS receives the final result only +after completion, per the owner's explicit instruction. + +## Cause and implementation + +Upstream orbital pan translates in the camera's screen plane. Consequently the +target can move above the ground grid; scrolling then approaches that floating +target rather than the intended ground location. Upstream also limits orbital +zoom-out to five scene diagonals. The application previously inferred a camera +from DOM deltas; that approximation was not the rendered native camera. + +ADR 0052 admits a bounded patch to the pinned upstream 0.36.3 WebViewer: + +- Project orbital pan onto the native grid plane and translate the complete rig. +- Anchor untracked orbital views before rendering; stop interpolation when the + operator navigates. Ordinary orbit keeps the target fixed. +- Zoom to that target independently of scene extent, retaining only 0.02 m + near-plane protection and a finite arithmetic guard. +- Read the native rendered eye through the existing disposable iframe boundary. + Remove the approximate DOM camera journal and its viewport/radius plumbing. +- Preserve first-person navigation and explicit follow/reset/plan actions. + Display-only updates carry the actual native eye and do not seek/reload data. + Browser QA caught that omitting eye fields on blueprint activation restored + the incoming store's startup camera despite the stable view identity. The + settings boundary now preserves the operator eye explicitly. + +The existing canonical hint describes LMB orbit, RMB ground-pivot pan and wheel +zoom. No new product controls, renderer, debug UI or alternative event loop were +introduced. The product UI skill kept this change inside the shared viewer. + +## Build and evidence + +Pinned upstream commit: `6ded109d33c549e98185f7c95fa8009d44e4adef`. +Portable patch and generated artifacts are under +`apps/control-station/vendor/rerun-web-viewer-0.36.3`. + +Worker006 used a temporary 4-CPU / 16-GB build-only container, no GPU, ports or +scanner data. Native Rust camera tests passed **6/6**, including fixed-pivot +rotation, ground pan, another grid orientation, top-down pan, zoom from over +20 km to below 3 cm and invalid-input / first-person regression. + +The first browser build failed because macOS AppleDouble archive metadata was +interpreted as a WGSL shader. The versioned packaging/build step now excludes +and strips that metadata inside the disposable build workspace. No shader or +upstream dependency was changed to bypass the error. + +The three source files on Worker006 were SHA-256 identical to the locally +audited patch result. `navigation-build.json` records source, toolchain and +paired JS/WASM identities. The installer validates the entire set before writing +and rejects package drift rather than silently applying a patch to a new SDK. + +## Acceptance record + +- Native tests: **6/6** passed. The source patch applies cleanly to the pinned + pristine upstream archive. +- Final application architecture: **4/4**; TypeScript: passed; complete frontend + suite: **895/895**, no skipped tests; production build: passed (14.36 s). +- Installer ran twice successfully. Generated JS/WASM ABI and all artifact + hashes passed. Served build uses WASM SHA-256 + `2b661de545ac90bb950eda1a28fc5a7c3ce4ca7f5bb24894789acf7589495185`. +- Real in-app browser, canonical port 8000, `JA-STROITEL-SUN-RING-002`, paused + at 60 s: orbit, close/far wheel zoom, point size change to 0.5 px, cloud layer + off/on, follow on/off, expanded scene and return all exercised. After the + settings-boundary correction, the manually rotated view survived display and + layer changes. No browser console errors were reported. +- Host-focused Escape returned from the expanded scene. The automation did not + establish Escape delivery with focus inside the native canvas; that gesture + is not claimed as verified. No keyboard code was changed in this increment. +- Physical two-finger/right-button dragging is not exposed by the available + browser automation. Ground-plane pan and fixed-pivot invariants are covered + by the native tests; the owner's actual trackpad feel remains manual acceptance. +- The final recorded scene is left paused for owner review. The temporary + Worker006 build container was removed after artifacts/logs were copied. + No alternate backend or temporary renderer was left running. + +The owner subsequently accepted navigation ("устраивает") and authorized OPS +documentation. Card #74 now records the accepted native patch, build manifest, +upgrade/rollback procedure and verification boundaries. This does not qualify +rover operation or alter the navigation test limitations above. + +## Boundaries + +The guide grid is a geometric plane, not measured terrain. Explicit following +may move a target because that is its requested purpose. Mission Core admits one +active 3D pane per iframe; multiple simultaneous panes would require a view-id +argument for snapshots. Floating-point/near-plane safeguards are retained; +"no route-size zoom cap" does not mean infinite numeric coordinates. + +The accepted full-fidelity RRD and 30-minute accumulation are unchanged. Raw and +corrected map versions do not need regeneration for this navigation update. diff --git a/docs/audits/2026-09-21-recorded-cloud-fidelity-and-navigation.md b/docs/audits/2026-09-21-recorded-cloud-fidelity-and-navigation.md new file mode 100644 index 0000000..dc565d3 --- /dev/null +++ b/docs/audits/2026-09-21-recorded-cloud-fidelity-and-navigation.md @@ -0,0 +1,130 @@ +# Recorded cloud fidelity and navigation — 2026-09-21 + +## Scope and evidence + +Operator report: JA-STROITEL-SUN-RING-002 is slow to reopen, its cloud is much +sparser than the live scan, point-size changes do not appear effective, the +accumulation control stops at 120 s, and the orbital pivot moves off the grid. +No scanner commands, raw-cloud changes, alignment-threshold changes, or map +correction changes are part of this work. + +The large ring has 13,608 captured point frames and 49,217,854 points over +1,446.684 s. The selected corrected map remains generation +`99f7b84541875a23aa3a8c58eb246116b66f8af03f1d2656eba4b527af36b1db`. +These are captured samples, not a count of unique surface points. + +## Confirmed causes + +1. The v12 RRD exporter retained only frames 1, 6, 11, … . Normal point batches + were complete, but four out of five captured scans never reached playback. + Another stride of four applied above 100,000 points per individual frame. + Neither rule altered the immutable raw capture. +2. Ordinary recorded-view settings appended blueprint rows without activating + the updated blueprint. Upstream Rerun uses an active clone; updates need + activation. The LAB costmap path already requested this, ordinary recordings + did not. A visible slider value was therefore not evidence of native state. +3. Both accumulation controls clamped to 120 seconds, independently of the + duration of the source. +4. Preparing the already-published v12 recording took 6.290 s on the first API + request (including integrity checks), then 0.006 s on the next request with + the same 182,745,691-byte RRD digest. No repeated map correction or RRD export + was observed in those requests. This is **not** a browser-open timing: the + disposable native viewer still reads and indexes the RRD when opened. +5. Ordinary display requests sent a playback cursor, causing a full RRD bounds + scan even though the eye did not change. Only tracking-relative camera + transitions now send that cursor. +6. Global HTTP gzip recompressed native RRD chunks; the stock 64 KiB file + response also incurred thousands of thread/event-loop handoffs competing + with telemetry/color work. RRD routes bypass gzip, and pinned file responses + use bounded 1 MiB reads. JSON/text compression, Range, ETag and cache pins + remain intact. + +## Implementation + +- v13 RRD projection retains every point of every captured frame. Color-only + overlays follow the same timestamps and complete point ordering. Only the + derived cache version changes; old projections are not accepted as v13. +- Both existing accumulation controls use one shared 1,800-second maximum, + with minute/second labels. The control changes the visible history; it does + not delete or decimate archived points. Existing chosen values are retained. +- Every recorded display update requests blueprint activation. Stable view + IDs remain; ordinary display changes do not inject a guessed camera pose. +- Export/cache failures remain explicit. There is no silent low-quality + fallback. More complete data necessarily increases file and memory size. + +## Navigation boundary / pending decision + +Rerun SDK and web viewer remain unmodified 0.36.3. Upstream `eye.rs` pans in +the screen plane, not the XY ground plane. Its orbital zoom approaches the +current look target; it does not fly through that target toward a remote +object. The public WebViewer API cannot read/write the active eye. The local +camera journal is a shadow estimate, not the native camera controller; changing +that estimate alone would not fix ground-constrained pan and would risk more +desynchronization. + +The requested ground-constrained pivot and unchanged pivot during orbit need +a deliberately supported native navigation extension (with pinned source, +patch, build provenance and upgrade tests), not per-pointer HTTP blueprint +replacement or another overlay renderer. Owner approval for that change to +the unmodified-upstream architecture was requested. Native navigation is not +claimed fixed by this increment. + +## Validation + +- Architecture tests, full TypeScript check and 889 frontend unit tests passed. +- Production build passed (existing large-bundle warning remains). +- Focused Python export/color/cache/corrected-map tests passed. New checks prove + all 12 synthetic cloud frames are actually logged, all six color frames are + emitted, corrected positions are retained for every frame, 0.5 screen-space + radius is serialized, and a 30-minute history does not write eye controls. +- Real v13 export completed. A streaming audit counted exactly 13,608 frames + and 49,217,854 positions, matching capture metadata. Size 743,853,833 bytes; + SHA-256 `40dbebba5dddccf94ba5c48118a94909bdb01ee8de883746d0db54a5d4ea86f2`. + A repeat prepare request reused this digest in 16 ms, without another export. +- Incremental gates: 81 frontend tests, full TypeScript check and production + build passed; 12 focused HTTP/session tests passed, including unchanged RRD + bytes, 206 ranges, length, ETag, pin release, overlay POST and retained JSON + gzip. No extra integrated backend was started. +- Bounded real HTTP sample: an 8 MiB range took 7.274 s with 64 KiB reads while + the archive/color load was active. After the 1 MiB change, cold metadata + restoration took 5.264 s and the body another 6 ms; the warm repeated request + took 13.9 ms total. These differ in background load and are not a controlled + throughput benchmark or a complete browser-open measurement. +- Browser acceptance on the canonical IAB viewer: after the transport fix, + the complete 24:06.684 timeline and dense playing cloud were visible at the + first inspection roughly 45 s after open (upper bound, not a precise load + stopwatch). Before the chunk-size fix, a 5+ minute observation still had no + usable timeline. Native color defaults remained visible while the first + requested height/Viridis overlay was preparing. +- Paused playback at 60 s: 0.5 → 5 → 0.5 changed the actual native point + footprint; a manually orbited camera retained its viewpoint through these + settings changes. Expanded cloud/Escape restore worked. The existing + display modal was used; its product anatomy was not changed in this task. +- The 1,800-second slider showed “30 мин”; seeking to the exact archive end + kept earlier geometry visible. However, this full-cloud draw took roughly + 23 s for the UI capture and swap rose from about 9 GB to 14.8 GB. The viewer + was closed immediately to release resources. This is functional acceptance + of the range, **not** smooth full-ring interaction acceptance on the 18 GB + Mac. No hidden decimation or lowered accumulation limit was substituted. + +## Remaining work and limits + +- Ground-constrained pivot, fixed-target orbit, and map-independent zoom are + not implemented; they require the native-controller decision above. +- First non-default coloring still decodes the complete source into a bounded + overlay cache and can take minutes on this capture. The existing index cache + is smaller than the complete 49M-point index. Opening no longer waits for + this to show the base cloud, but color-preparation latency is not solved. +- Full-fidelity storage is not a sufficient large-scene rendering strategy. + A follow-up should separate a whole-route overview from full-detail spatial + inspection, with explicit level-of-detail/provenance and access to every raw + point. Do not reintroduce silent frame skipping as a performance “fix”. + +## Resource and authority policy + +Single canonical service on port 8000; no Docker startup, extra viewer, load +test, device command, changed raw evidence or regenerated corrected map. +Tests, build, large export and browser QA run sequentially on the 18 GB Mac. +The full-cloud view is not a guarantee of interactive performance at every +history length. Any future LOD policy must be explicit and must preserve a +full-fidelity inspection path. diff --git a/docs/audits/2026-09-21-ring-continuity-and-live-lifetime.md b/docs/audits/2026-09-21-ring-continuity-and-live-lifetime.md new file mode 100644 index 0000000..eaecce0 --- /dev/null +++ b/docs/audits/2026-09-21-ring-continuity-and-live-lifetime.md @@ -0,0 +1,103 @@ +# Full-ring continuity and live-pass lifetime + +## Scope and evidence + +Owner requested review of the latest full ring, removal of distance-triggered +termination, and removal of the recorded/live pass chooser from operator setup. +Full-route acquisition and its quality gates are deliberately unchanged. + +The completed study is `0324337e-b590-4561-b19a-8fbc7835b888`, captured on +2026-09-21. This is an audit of immutable production decisions, not a numerical +rerun, new capture, or absolute-position survey. The operator reports a start +approximately 20 m before the original seam and a full circular walk. + +Private evidence is retained under +`.runtime/audits/2026-09-21-ring-002-unbounded/`: audit script, per-check results, +UTC/monotonic audit bounds, input SHA-256 inventory and test logs. Every study +artifact, the raw MQTT capture, metadata, clocks and capture manifest were hashed +before and after inspection; they were unchanged. Raw data remains outside Git. + +## Production result + +- Reference length: **579.9827 m**; cumulative live travel at termination: + **580.0751 m**. Recorded reason: **`distance-limit`**. +- Route search: **810/810** fits, complete; selected anchor at **555 m** along + the reference. Worker wall time **127.93 s**, followed by three fresh + confirmation checks. First tracking receipt: **12:35:02.035 UTC**. +- Fresh checks: **101 accepted / 101**, comprising two acquiring decisions and + **99 tracking decisions** (the third confirmation establishes tracking). + No reinitialization or recovery attempt; no recorded pose receipt gap. +- Overlap within the 0.5 m evaluation radius: **98.274–99.928%**, median + **99.644%**. Inlier surface RMSE: **0.1330–0.1580 m**, median **0.1416 m**. +- Successive accepted transform changes: maximum **0.0716 m / 0.2732°**. + These are consistency measurements, not surveyed pose errors. +- Local checks: median **0.450 s** worker time, maximum **0.900 s**; + median check interval **5.111 s**, maximum **6.564 s**. Maximum accepted + result age **3.112 s**, below the unchanged freshness gate. +- No LiDAR/pose queue overflow or oversize rejection. Camera preview dropped + 1,271 superseded queue frames; this is a separate latest-frame video path, + not evidence of missing registration clouds. +- Last check at **12:43:25.926 UTC**, at sampled travel **578.815 m**, was + accepted and tracking. The distance branch terminated the study **238 ms** + later, at **12:43:26.164 UTC**. Capture finalization completed separately at + **12:44:51.043 UTC**. No scanner-stop command belongs to that distance branch. + +The stop is therefore an execution-policy defect, not a failed geometric fit. +The reported black screen was not independently reproduced; saved aligned +geometry and the historical scene remain available. Browser telemetry is a +presentation-admission proxy, not a GPU-paint receipt. + +## Correction and architecture boundary + +`selected-live-route/v2` separates reference coverage from pass lifetime: + +- The selected reference still defines where matching searches and validates. +- Distance remains telemetry. It does not consume a budget or end a pass, + including on detours, reversals, snakes or additional laps. +- New runs expose `maximum_distance_m: null` and `maximum_seconds: null`. + The live loop no longer reads a distance cap, including a retained legacy + finite field. Historical reports are not migrated or rewritten. +- Explicit study cancellation, source end/stop intent, changed source identity, + technical failure and service shutdown retain their existing semantics. + Quality/freshness loss still revokes tracking and invokes the existing + recovery path; removing the cap does not hold a false green status. +- Engineering recorded-replay bounds are unchanged. They limit an offline + experiment, not operator live travel. + +Operator setup now contains project, full reference and direction, followed by +one **«Начать новый проход»** action. The entire **«Повторный проход»** section, +its mode switch, recorded-source fields and explanatory paragraphs are removed. +The workspace invokes only the live start and no longer mounts the recorded +comparison hook or requests its catalog. Existing saved comparisons and backend +engineering comparison APIs remain available. The admitted Design Guideline +Inspector and Button are reused; no new controls, styling or navigation added. + +## Validation and limits + +Functional fixtures exercise three traversals of the same route geometry with +travel equal to three reference lengths, including a finite legacy cap field. +They verify that source end, explicit study cancellation and source stop intent +still terminate and release leases without commanding capture. A second fixture +uses the real stationary bootstrap and causal gate, synthetic numeric fits, and +fresh accepted windows beyond three reference lengths. UI tests render the +actual settings, verify empty/busy/short-reference gates and the direct start. + +Acceptance: **140 focused backend tests**, **888 frontend tests**, architecture +checks, TypeScript and the production build passed. The existing large-chunk +build warning remains. In-app browser QA on the canonical `8000` service verified +the empty form, full corrected reference, normal/expanded layouts, scrolling to +the start action and Escape. No start action was sent during browser QA. The +latest real ring result was left open, with its saved geometry visibly loaded. +The service was restarted while acquisition was idle and left healthy on `8000`, +with no Mission Core listener on `8765`. + +SHA-256 comparison against the preceding handoff confirms unchanged entry +acquisition, full-route search/worker, stationary bootstrap, registration and +causal-tracking modules. Only lifetime policy/loop and the requested setup UI +were changed for this increment, alongside regression tests and this audit. + +The observed physical ring supports continuous localization on this reference. +It does not establish absolute pose accuracy, unattended navigation/safety, +seasonal transfer, multi-kilometre capacity, or physical multi-lap acceptance. +No new device operation or change to route search, correction-map producers, +registration thresholds or scanner acquisition was made for this correction. diff --git a/docs/audits/2026-09-21-ring-entry-and-seam-review.md b/docs/audits/2026-09-21-ring-entry-and-seam-review.md new file mode 100644 index 0000000..3169546 --- /dev/null +++ b/docs/audits/2026-09-21-ring-entry-and-seam-review.md @@ -0,0 +1,181 @@ +# Ring entry failure and independent seam traversal + +## Decision + +The latest `JA-STROITEL-RING-001` live study supports local tracking across the +corrected reference seam, but exposes a route-wide cold-start scheduling defect. +Its first location was not geometrically unsuitable: a complete offline search +of the original first prefix finds an unambiguous 98.82% overlap candidate with +unchanged production quality gates. The live search never reached that seed. + +No production code, default, threshold, scanner state, saved run or corrected +reference was changed during this audit. Diagnostic outputs are private, ignored +runtime evidence; the original study retains its actual unsuccessful first +attempt. This is not a retroactively successful live initialization. + +## Inputs and scope + +- Latest study `473870e0-5210-452c-b9e4-9d7937e93646`, query capture + `20260921T103511Z_viewer_live`. +- Reference: `JA-STROITEL-SUN-RING · коррекция v2`, the full 579.9827 m, + all 5,182 poses, 694,420 registration-map points. This was not another + truncated-reference regression. +- First stationary prefix: 8,870 retained query points, approximately 10 seconds, + maximum measured prefix motion 0.01066 m. No recorded receipt gap. +- Two initialization attempts, one operator reinitialization, final study state + `completed`, termination `spatial-stop-requested`. +- The operator deliberately returned to the initially rejected physical place. + Initial/final poses differ by 0.164 m in this capture's SLAM coordinates. This + supports the comparison but is not independent survey ground truth. + +## Execution chronology (Moscow time) + +| Time | Recorded event | +| --- | --- | +| 13:35:37.912 | First stationary prefix begins | +| 13:35:47.921 | First search starts | +| 13:36:24.105 | Search rejected as `initialization-incomplete` | +| 13:36:46.859 | Operator requests reinitialization at the familiar start area | +| 13:36:56.780 | Second search starts | +| 13:37:14.769 | Dense-start candidate found: 98.72% overlap | +| 13:37:27.973 | Third disjoint fresh check establishes tracking | +| 13:38:35–40 | Trajectory crosses the original start/end seam in reverse | +| 13:39:36.468 | Last accepted tracking result near the initial failed location | +| 13:39:41.876 | Intentional STOP completes the study | + +The final in-flight calculation also produced a geometric candidate, but was +correctly not published after STOP. Its unaccepted decision is not a loss event. + +## Root cause of the first failure + +The code first runs 108 dense-start seeds. That stage consumed 15.737 seconds +and found no admissible candidate, as the actual location was about 15 m before +the seam, not within the familiar start patch. The route-wide stage then received +only the remainder of a shared 35-second search budget: approximately 19 seconds. + +The full route contains 117 searchable anchors and three yaw hypotheses per +anchor (351 fits). Retrieval covered the full map, but precise verification +completed only 40 anchors and one fit of the next anchor: 121/351 route fits. +Together with the start stage this is 229/459 planned fits. The saved result +explicitly says `complete=false`, `incomplete-route-search`; it does **not** prove +`no-route-location`. + +The nearest anchor was ranked 62nd. The anchor whose existing production seed +eventually finds the correct fit was ranked 100th. Neither was reached live. +The coarse descriptor ranks radial/height distributions around cloud medians, +not a measured probability of place identity. Here it prioritizes other route +areas over the true place. Different visible geometry in a stationary prefix +and a multi-visit local reference makes that representation a plausible source +of poor ordering; this audit establishes the bad ordering, not vegetation as +its independently classified cause. + +There are two further implementation consequences: + +1. `choose_route_location` discards the candidate queue when a search is + incomplete; `StationaryBootstrap.offer_prior` enters `lost`. Initial loss + then waits for operator retry rather than continuing the unexamined queue. +2. The incomplete/expired operator message asks to stop recording and begin + again. That recommendation is inappropriate for this observed compute + exhaustion. No scanner reset or bad physical location was established. + +## Bounded offline experiments + +All original run-file hashes were verified before and after each experiment. +Single CPU job at a time, one numerical thread; no hardware, ingress or UI +publication. Each output is exclusive-created, never written over live evidence. + +### 1. Retrospective geometric check + +Register the original first prefix using a seed from the later successful +alignment. This uses future information deliberately and is **not** a cold-start +test. It isolates whether the first cloud is compatible with the corrected map. + +- First accepted transform as seed: 98.887% overlap, 0.1396 m inlier RMSE. +- Last accepted transform as seed: 98.893% overlap, 0.1399 m inlier RMSE. +- Both pass unchanged local registration gates. + +### 2. Complete cold-prefix route search + +Replay the same original first query and complete reference through existing +`relocalize_route`. No endpoint, final transform or physical-location hint enters +the search. The only experimental override is a 120-second **offline** deadline +so the finite queue can finish; all geometric quality gates remain unchanged. + +- All 117 anchors / 351 fits completed in 59.408 seconds. +- Correct, non-ambiguous leading candidate: 98.820% overlap, 0.1439 m inlier RMSE. +- Candidate anchor at route progress 560 m, ranked 100th; the fitted pose is + consistent with approximately 15 m before the seam. +- Next distinct fitted candidate: 61.086% overlap at an unrelated route area. + It was already present in the incomplete live search. Thus accepting the + first threshold-passing fit or loosening acceptance would be an unsafe fix. + +This proves recoverability from the original prefix, not live readiness. A +59-second result would violate the existing live 35-second search / 40-second +source-age contract and must not simply be relabeled fresh or green. + +### 3. Seed-construction sensitivity + +On a bounded, retrospectively chosen four-anchor subset, compare the existing +cloud-median translation seed with a seed mapping query scanner origin to the +route anchor, using the same yaw options, targets and quality gates. + +At the nearest anchor (565 m), yaw 90°: + +- Existing median seed: 53.799% overlap, rejected. +- Origin-to-anchor seed: 98.847% overlap, accepted geometrically. +- Retrospectively measured initial-position error decreases from 4.62 to 1.51 m. + +This is evidence for improving seed construction, not an approved replacement: +the subset was chosen after examining the result, and one neighboring anchor +still produces a weak approximately 61% candidate. Global ranking, competing +places and fresh-data confirmation remain necessary. + +## Tracking and seam quality + +- 28 fresh checks accepted, including initial confirmation; 26 accepted results + in tracking state. No transition to lost/recovering after tracking begins. +- Observed distance after reinitialization: 101.633 m; last accepted result at + 100.896 m. The remaining final result was fenced by intentional STOP. +- Overlap within 0.5 m: minimum 98.516%, median 99.348%, maximum 99.746%. +- Inlier surface RMSE: 0.1226–0.1533 m, median 0.1422 m. +- Maximum accepted consecutive-transform change: 0.0316 m / 0.5906°. +- Calculation worker wall time: median 0.383 s, maximum 0.464 s. The roughly + five-second interval is the configured check cadence, not a five-second fit. +- At the seam crossing, overlap remains 99.57–99.69%, with centimetre-scale + transform corrections and no recovery event. Route progress wraps from the + beginning to the end as expected while physical coordinates remain continuous. + +These metrics measure consistency with the admitted map, not absolute rover +position accuracy. Only the traversed seam neighborhood is independently +checked here, not the entire 580-m ring, arbitrary seasons, or kilometre routes. + +## Recommended architectural correction (not implemented here) + +1. Preserve the successful local tracking and corrected reference. +2. Make full-route acquisition a resumable search with a cached reference index + and auditable unexamined candidates. A compute slice expiring means still + searching/incomplete, not a declaration that the operator picked a bad place. +3. Separate place-hypothesis generation from live position validity. Refresh + observations and confirm candidates against disjoint current frames; never + extend an old matrix's control authority to cover a long global search. +4. Qualify sensor-origin-based descriptor/seed variants against this recording, + previous successful start/mid-route runs and wrong-place negative cases. +5. Retain ambiguity, identity and quality gates. Do not patch this by lowering + overlap or merely increasing the shared timer. + +The saved first prefix, complete map, successful return and disjoint subsequent +frames are sufficient for the next offline iteration. No new field recording +is required to diagnose or begin correcting this failure. + +## Validation and private reproduction + +62 focused tests passed: route relocalization, stationary bootstrap, stationary +recovery, and planning-live lifecycle. Existing tests correctly assert refusal +of an incomplete search; they do not establish acceptable completion latency +on this real ring. The new captured case should become an evidence-backed +acceptance fixture for the acquisition redesign. + +Private evidence: `.runtime/audits/2026-09-21-ring-001-entry/` contains +`inspect.json`, `exhaustive.json`, `seeds.json`, the diagnostic `audit.py`, and a +provenance seal. Inputs remain in the canonical run directory. No temporary +worker remains; canonical Mission Core stays on port 8000. diff --git a/docs/audits/2026-09-21-shared-ring-closure.md b/docs/audits/2026-09-21-shared-ring-closure.md new file mode 100644 index 0000000..807b106 --- /dev/null +++ b/docs/audits/2026-09-21-shared-ring-closure.md @@ -0,0 +1,139 @@ +# Shared known-revisit correction for small and large rings + +## Scope and topology correction + +The operator clarified that the small ring is inside the large ring and shares +roughly half its path. Their unshared corridors must not be scored as a failed +repeat of the same map. Reference surveys in Data and independent captures +owned by planner studies are distinct evidence classes. + +The earlier 30/101 replay used the planner-owned `JA-STROITEL-RING-002` full +small-ring pass, not the Data reference itself. Its whole path does not become +covered merely because its origin is the planner. The earlier 29/29 short pass +also came from the planner. Preserve the negative numbers, but classify their +geometric scope correctly. Other short planner passes are separate queries. + +## Implementation and boundaries + +`reconstruction/closure.py` owns a device-independent, injected-registrar policy +for acquisition of a known start-area revisit. It has no hardware, UI, catalog +or planner import. The K1-specific `reconstruct_recorded_ring.py` adapter now +uses this same policy on both source recordings, with no source-name branch. + +The separate offline first-fit registration policy admits a larger initial +correction (25 m / 180 degrees) without modifying live tracking's 3 m / 30-degree +policy or the existing overlap, RMSE, shape, information and correspondence +criteria. These are declared acquisition bounds, not a promise of arbitrary +drift recovery. Identity is the initial hypothesis; endpoint positions are +never forced together or used to manufacture a seam transform. + +All 12 training-only support pairs are evaluated: first [10,20,40] seconds × +last [5,10,20,30] seconds. Held-out frames cannot enter either side; shared +source frames are rejected. Each qualified forward fit must pass seeded reverse +refinement and the existing 0.1 m / 0.2-degree round-trip check. Qualified support +windows must also agree at both observed patch centers within the separately +declared 0.5 m / 1-degree consistency envelope. This is an engineering ambiguity +guard, not a calibrated accuracy claim. + +The established 20/5-second measurement is retained if qualified. Otherwise the +largest qualifying support is selected. All windows are still examined for +contradiction before selection is accepted. The smooth field solver, rigid +per-frame transform, original-frame clock binding and full-resolution export +are unchanged. Failure keeps the audit and produces no silently accepted +identity correction. + +The adapter's v2 contract binds closure search and its declared policy. +Sealed v1 decoded caches remain reusable only when all actual decode/holdout +settings match. `review_recorded_ring.py` adds explicit frozen-data acceptance: +all local windows qualify, enough seam holdout exists, fixed quality gates pass, +and cross-visit agreement is not degraded. Packaging requires positive v2 +acquisition and review, with the complete search included in the evidence seal. +Previously published v1 map bundles and pinned studies remain supported. + +This is an offline correction/admission workflow, not a newly installed +background auto-corrector for every capture. It does not discover arbitrary +interior loops or recover a measured drift history from native LiDAR sweeps. + +## Regression caught and retained + +The first generalization always selected the largest qualifying support. It +passed same-source checks (small 52/52, large 136/136), but warm replay of the +independent full small-ring planner pass accepted only 100/101 snapshots. The +96.115 m window failed numerical convergence despite 99.866% overlap and +0.13993 m RMSE; the following window recovered. The same replay against its +frozen previous reference accepted 101/101. This was a real numerical regression, +not an uncovered-region excuse or permission to ignore convergence. + +The final cascade preserves the qualifying established window and uses expanded +support only when necessary. It reproduces the previous small-ring corrected +points and trajectory bit-for-bit, and the previous successful large-ring +diagnostic geometry bit-for-bit. The unsuccessful intermediate candidate and +its full replay are retained privately; no quality threshold was weakened. + +## Verification and runtime + +- 201 focused software tests passed, including 23 new closure/admission cases + and the previous registration, correction, route search, causal recovery, + reference version and session-default suites. One existing Starlette/httpx + deprecation warning remains. Ruff and diff whitespace checks passed. +- Final real-source qualification uses the same immutable small 579.797 m and + large 1595.409 m recordings and their sealed decode caches. No new scanner + recording, hardware command, frontend change or service restart is required. +- The shared policy retains the small 20/5-second closure and selects 40/30 + seconds for the large ring. Source frame holdout remains correlated by the + upstream K1 map and is not independent survey truth. +- Final same-source local checks pass 52/52 on the small ring and 136/136 on + the large ring. Frozen seam holdout overlap is 89.434% and 95.093%, respectively. +- Repeating the independent full small-ring planner capture against the final + rebuilt small atlas restores 101/101 numerical and causal acceptances, matching + the 101/101 frozen-reference baseline. This is a warm map-version regression, + initialized by its existing old-map cold fit, not another cold-start claim. +- Complete large-map atlas and trajectory arrays equal the previously tested + diagnostic atlas. Its independent short-pass 29/29 and cold numerical search + evidence remain applicable by exact identity, not by assuming similar maps + have identical behavior. No full independent pass through the new territory + is inferred from that reuse. +- A full numerical search for the planner's `ja-sun-009-100m` was stopped after + host swap increased. Its exact temporary worker was terminated and reaped; + canonical service 8000 was retained. This incomplete run is neither a + localization success nor a geometric rejection. No repeated heavyweight + search is required to establish equivalence of identical map arrays. + +Private evidence is under `.runtime/audits/2026-09-21-closure-v2/`, with the +initial candidate retained at the root and final cascade results separately +under `cascade/`. Earlier observations and source recordings remain immutable. + +## Reviewed version admission + +The existing packager rechecked all 13,608 full-resolution large-map frames and +all 13,624 poses against the frozen field, plus source transport/clocks and the +complete review seal. It published a separate content-addressed full map. The +ordinary large-session selection now resolves to its corrected generation and +1599.807 m in the canonical planner API. The small-session default pointer is +unchanged; existing planner studies retain their pinned generations. Raw captures +remain available, and no vehicle-control authority is granted. + +Canonical API acceptance confirms that the spatial overview defaults to +`corrected` and uses the same admitted map generation as ordinary planner +selection. The original-generation-pinned view remains `original`. Both +Original and Corrected preview RRDs were rendered successfully at the 80 m +height ceiling. Browser visual QA and a new full playback export were not run. +The final private evidence seal rechecked 212 input files with no changes, +no producer changes, and exact small/large geometry equivalence confirmed. + +The operator workflow remains explicit: reconstruct → frozen review → package → +publish paired overview → activate the session default. No new UI or background +automatic capture correction has been introduced. Publication and admission +reuse the existing ordinary-name selection and Original/Corrected inspector. + +## Decision and remaining scope + +The shared cascade is qualified for these two known start-area revisits, with +explicit fail-closed acquisition/review boundaries. Universal means one policy +and regression corpus, not a claim that every possible ring will close. + +Independent new-territory repeat accuracy, absolute geometry, the actual spatial +distribution of drift, arbitrary crossing-loop graphs, seasonal transfer and +onboard/live timing remain unproven. One accepted seam plus a smoothness prior +cannot establish those facts. Incomplete optional planner searches remain open; +do not relabel them as passed or use mixed-coverage loops as an aggregate score. diff --git a/docs/audits/2026-09-21-smooth-map-correction.md b/docs/audits/2026-09-21-smooth-map-correction.md new file mode 100644 index 0000000..1214993 --- /dev/null +++ b/docs/audits/2026-09-21-smooth-map-correction.md @@ -0,0 +1,157 @@ +# Offline smooth correction of a recorded ring + +## Decision and boundary + +Implemented and exercised an **experimental map derivative**, not a live SLAM +replacement, planner change or automatic reference promotion. The real input and +all detailed geometry remain under ignored private runtime storage. No scanner +command, capture mutation, viewer publication or vehicle command was made. + +The result supports proceeding with review of a corrected reference. It does not +establish absolute positioning accuracy or independent repeat-pass acceptance. + +## Implementation + +- `src/k1link/reconstruction/smooth_correction.py`: vendor-neutral smooth field + C(s), where s is cumulative distance in the original recorded trajectory. +- `experiments/reconstruct_recorded_ring.py`: K1-specific immutable-source adapter, + source hashing, frame split, surface measurements, candidate solver, local + registration checks and full-resolution derivative materialization. +- `experiments/review_recorded_ring.py`: fixed-population cross-visit holdout and + fresh-frame checks of sensitive matching windows; no re-fit of the correction. +- `tests/test_smooth_map_correction.py`: synthetic invariants and negative cases. +- `pyproject.toml`, `uv.lock`: optional `map-correction` extra with SciPy 1.16.2 + and the existing small-gicp 1.0.1. No new mandatory runtime import. + +Capture remains plugin-owned. Reconstruction has no plugin, UI, planner or +hardware imports. Planning may later consume a separately admitted derivative; +there is deliberately no runtime path that changes the current reference. + +## Model, not recovered ground truth + +The field has natural cubic splines for translation and rotation-vector +parameters, 20 m knot spacing, and an explicitly recorded first/second derivative +penalty. Rotations act about a geometry-bound pivot so changing world coordinates +does not change the physical answer. Three-point Gauss quadrature integrates the +squared spline derivatives; the first knot fixes the gauge. + +A surface link measures C(query) = C(reference) × T(reference, query), evaluated +at the observed patch center and in orientation. No endpoint-position equality, +flat-ground constraint or endpoint-derived initial guess is introduced. The +weights are engineering regularization scales, **not sensor covariance**. + +Each whole source cloud frame receives one rigid transform, and corresponding +pose positions/orientations receive the same field. Thus within-frame distances +are preserved, apart from float32 serialization. Different frames may move +relative to one another; that is precisely the deformation being tested. + +This field is parameterized by traversal, not a single XYZ warp that would move +two different visits identically. Small-rotation chart admission is explicit; +large corrections, arbitrary loop discovery and scale to multi-kilometre graphs +remain unqualified. Current CPU least-squares uses a dense numerical Jacobian. + +## Input and separation + +The private ring source has 5,178 cloud frames, 5,182 poses and 17,669,672 points, +with a 579.797 m recorded trajectory. Full decode and source SHA-256 checks +precede and follow the operation. Source sequences are continuous. The export +preserves every point; the solver/validation sample every eighth source point +and use 0.25 m voxels. Spatial windows are declared computation profiles, not +capture-distance or vertical-clipping limits. + +Every ten seconds, the two-second interval at phase 4–6 seconds is withheld: +1,061 frames withheld, 4,117 retained. No withheld frame enters the reference map +or the surface-link fits. Upstream K1 mapping still makes these observations +correlated; this is neither an independent recording nor ground truth. + +Cloud/pose binding uses the existing host-monotonic chronology. Nearest pose +receipt distance is 25 ms p95, 143 ms maximum. This is not proven hardware +synchronization and does not recreate per-point firing times or native sweeps. + +## Actual constraints admitted + +The start-area revisit qualifies on retained frames with the unchanged production +GICP policy: 98.70% overlap within 0.5 m and 0.120 m inlier RMSE. Forward/reverse +fits differ by approximately 5 mm at the patch center and 0.191 degrees. + +All **29 attempted additional neighboring-window registrations were rejected** +under the unchanged overlap/RMSE gates. They were excluded, not assigned low +quality but positive identity. Therefore this result uses **one measured closure +and a smoothness prior**, not a densely measured multi-edge reconstruction of +the drift history. Rejected measurements are retained in the private audit. + +## Results + +| Check | Original | Corrected v2 | +| --- | ---: | ---: | +| Trajectory length | 579.797 m | 579.983 m | +| Final minus initial Z | −1.4646 m | +0.00265 m | +| Local held-out registration candidates | 51 / 52 | 52 / 52 | +| Median local overlap within 0.5 m | 98.259% | 98.285% | +| Median local inlier RMSE | 0.16246 m | 0.16192 m | +| Cross-visit held-out overlap within 0.5 m | 25.54% | 89.43% | +| Cross-visit all-point distance median | 0.891 m | 0.119 m | +| Cross-visit all-point distance p95 | 1.825 m | 0.906 m | + +The cross-visit comparison uses the **same 16,109 source points from 41 withheld +frames**, compared to only the first retained window, with the correction frozen +and no re-fit. The remaining outlier tail is real and is not explained away as +vegetation without classification. Millimetre endpoint-Z agreement is **not** +millimetre map or localization accuracy. Physical endpoint locations were not +measured and may differ; no exact physical endpoint offset was imposed. + +Maximum displacement of the sampled trajectory is 1.548 m. The maximum change +of displacement along it is 8.61 mm per travelled metre (p95 7.99 mm/m). This is a +correction-field gradient, not a measured strain bound for every 3D surface. +The maximum rotation-parameter gradient is 0.000786 degrees/m. Halving/doubling +the smoothness weight changes trajectory positions by less than 0.008 mm in this +one-closure case; that only shows this prior's numerical stability, not truth. + +## Registration sensitivity retained + +The first candidate had 51/52 accepted windows, with a non-convergence at 437 m; +the original had a non-convergence at 327 m. No quality threshold was lowered. +Fresh disjoint one-second halves in **both** regions passed for both maps. + +After changing the field's coordinate pivot and exact quadrature (v2), its geometry +is nearly unchanged but all 52 two-second windows pass. This is evidence of +numerical/window-composition sensitivity of GICP convergence, **not proof that +its failure mode has been fixed**. Both revisions and their less-favorable +results are preserved. Production matching/loss/recovery code is unchanged. + +The local test starts at identity, then uses the last accepted transform; it +does not seed each query with the correction field. The test is local matching, +not global place recognition or the entire live state machine. Agreement with +the generating field must not be called independent pose accuracy. + +## Reproduction and storage + +Install the optional extra through `uv` in a project environment. Run the +experiment with `--raw`, `--sha256`, `--session-id` and a fresh `--output` directory. +An optional `--cache` reuses a hash-sealed decoded source. Subsequent review takes +the result directory and can include additional explicitly named window groups. +Outputs are exclusive-created, not overwritten. + +`corrected-points.f32` is little-endian Nx3 float32 map coordinates. Frame offsets, +counts and receipt times are retained in `corrected-trajectory.npz`; `distance_m` +and `frame_distance_m` there are **original source traversal parameters**, not +recomputed corrected path lengths. Intensities are unchanged and remain in the +hash-sealed source cache. `correction.json` records the field including its pivot. +The review seal binds code files and measured artifacts by SHA-256. + +## Acceptance and next step + +- 17 focused tests pass, including existing registration regression tests. +- Ruff and diff whitespace checks pass. +- One sequential CPU job at a time; no duplicate server, Docker workload or + temporary worker remains. The canonical service stays on port 8000. +- Candidate only; neither stored session nor operational reference replaced. +- Next: versioned reference admission and independent repeat-pass verification, + retaining recoverable localization state for numerical matching failures. +- Ops documentation write is pending: instruction/project MCP reads timed out; + no card was changed and no raw API fallback was used. + +Subsequent same-day progress: the separate bundle and offline planning adapter are +verified in `2026-09-21-map-reference-version-admission.md`. Source/default live +selection remains unchanged. Ops metadata reads recovered, but card searches +still time out, so the engineering card update remains pending. diff --git a/docs/audits/2026-09-21-whole-reference-and-capture-catalogs.md b/docs/audits/2026-09-21-whole-reference-and-capture-catalogs.md new file mode 100644 index 0000000..25b993c --- /dev/null +++ b/docs/audits/2026-09-21-whole-reference-and-capture-catalogs.md @@ -0,0 +1,93 @@ +# Whole reference and independent capture catalog + +## Owner decision and product composition + +New planning executions use the complete selected reference recording. The +reference crop section, its range controls and its 30-m shortcut are removed. +Data → Sessions and records lists independent physical recordings; live passes +created by the planner stay with their planning studies and are not offered as +reference choices. No new type selector, tab, catalog copy or workspace is +introduced. This is an owner-approved projection change in existing surfaces, +using the existing Select, Inspector, Button and WorkspaceWindow components. + +The rejected alternative was another type dropdown in Data. It would preserve +the mixed acquisition catalog the owner explicitly asked to separate. Physical +evidence and corrected-map generations remain immutable. A planner pass is +still a physical capture, not a synthetic LAB result. + +## Reproduced defects + +- A newly selected reference was initialized to the first 30 metres. +- Choosing the same recording again reset its independently stored interval + to indices 0…1. React did not reload the unchanged source identity, so the + interval remained there. A stationary prefix then showed approximately zero + metres and two poses, disabling launch. The scanner lifecycle was not a + dependency of that button condition. +- The latest failed ring study is terminal (`cancelled`). Its frozen reference + was about 97.57 metres, not the complete approximately 579.98-metre ring. + Initialization exhausted its recorded candidates without finding a location. + This establishes the incomplete reference, not that full-reference matching + has already succeeded at the operator's latest location. + +## Implementation boundaries + +The planner derives its reference poses from the complete loaded source; it no +longer owns editable start/end state. Re-selecting the same source explicitly +reloads its generation without collapsing its route. Product saves request +`whole_recording: true`; the server resolves the endpoints from the immutable +source. Existing bounded API clients remain a compatibility path for historical +recorded experiments. Existing reports retain their frozen geometry. Opening +an older editable draft prepares a full-route revision rather than rewriting +the prior run. + +The physical session store adds append-only planning-acquisition links keyed by +exact session and run identities. New live binding records the link before +publishing its run state. Startup reconciliation backfills only explicit live +report bindings; names, distances and fit success are never classifiers. +The link may precede capture finalization/catalog ingestion, survives archive +reconciliation and project catalog tombstones, and is excluded from the physical +source evidence digest. + +The `standalone` catalog scope filters these links and LAB projections in SQL +before pagination. Data and new reference selectors request it. Existing +`source`/`all` scientific scopes and direct source/replay endpoints remain +unchanged. Recorded-repeat selection inside the planner still requests physical +`source` captures, so planning evidence does not become inaccessible after it +is hidden from Data. A previously independent survey is not reclassified just +because somebody later compares it offline. + +Live admission, registration thresholds, scanner commands and vehicle authority +are unchanged. The older offline recorded-comparison solver still admits short +references only; its existing constraint is now explained next to its disabled +action instead of silently disabling the full-ring form. This task does not +claim to upgrade that solver to route-wide offline relocalization. + +## Acceptance + +- Focused Python regression suite: 135 passed (capture catalog, drafts, live + planning, session store/API and corrected session defaults). +- Frontend: architecture/targeted checks passed, typecheck passed, complete + test suite 887 passed, production build passed. Ruff and `git diff --check` + passed. +- Canonical LaunchAgent restarted after the build. One backend remains on + `127.0.0.1:8000`; no temporary backend was started on 8765. +- Startup backfilled 19 exact historical acquisition bindings. The current + standalone catalog contains five independent recordings, including + `JA-STROITEL-SUN-RING`, and excludes the planning captures. The planner + still lists 22 projects, including the latest unsuccessful ring study. +- Browser acceptance used the existing application tab after reload. Data and + the reference selector both showed the five independent recordings; no + second type dropdown was introduced. The planning history retained the + latest pass and previous studies. +- A new unsaved form selected the corrected ring and showed `Вся запись · + 579.98 м · 5 182 положений`. With a name supplied, launch was enabled. + Re-selecting that same recording retained the full route and enabled action. + Normal and expanded settings, scrolling to the action, and Escape close + were checked. Browser error logs were empty. +- The temporary form name was cleared without saving or starting a pass. The + corrected ring preview remains open. No scanner command, new acquisition, + registration run or historical-report rewrite was performed during QA. + +These checks validate form/catalog repair and full-reference admission. They +do not claim a successful new field localization at the previously failed +location; that remains a short operator-controlled seam test. diff --git a/docs/audits/2026-09-22-recorded-display-profile.md b/docs/audits/2026-09-22-recorded-display-profile.md new file mode 100644 index 0000000..d87335e --- /dev/null +++ b/docs/audits/2026-09-22-recorded-display-profile.md @@ -0,0 +1,272 @@ +# Saved recording display profile · 22 September 2026 + +## Operator contract + +The existing Display window now offers cloud decimation, an accumulation-slider +range in minutes and the current accumulation duration. New session profiles +default to a three-minute slider range, zero decimation and the existing 12 s +current window. Exact value inputs accept fractional percentages and custom +durations; the 30-minute drag range is not a duration-validation ceiling. + +0% retains the original point count, 50% retains half per frame (rounded), and +100% hides points without hiding the trajectory or grid. These are presentation +controls, not scanner, registration, reconstruction or evidence controls. + +Recorded Display edits preview while the modeless window remains open. Changes +coalesce for 750 ms or flush on a control's commit; 0%/100% decimation bypass +that delay. Closing atomically saves the complete display settings in +`display-profile.json` inside that session's catalog-validated directory. Schema +is `missioncore.session-display-profile/v1`. Sealed `method.json`, raw transport, +corrected geometry and original RRD are not rewritten. Missing profiles use +defaults. Reads/writes are fenced by session identity and edit revision; writes +are serialized. Save failure leaves the window closable and reports an error. + +## Implementation / Rerun upgrade inventory + +- `packages/spatial-ui/src/{sceneSettings,ObservationTimeline,SceneDisplayControls}`: + shared RangeControl, three-minute default, per-profile timeline maximum. +- `apps/control-station/src/core/observation/{sessionDisplayProfile,useSessionDisplayProfile}` + and App: JSON mapping, validation, live preview and close-to-save session-bound persistence. +- `src/k1link/sessions/display_profile.py`, SessionStore and session_api: + confined GET/PUT display-profile endpoint, atomic fsync/replace sidecar. +- Plugin contribution `point_display_renderer`, K1 `recorded_point_display.py`: + read-only full timeline traversal, deterministic ranked sampling, full-frame + palette before selecting rows, corrected map-frame ownership retained. +- Follow-up fix `prepared_point_display.py`: intensity/Turbo decimation reads + exact positions and packed colors from the already prepared operator RRD via + `RrdReaderInternal`/Arrow instead of re-normalizing raw transport. The client + supplies the source SHA-256; the server validates and pins that exact ready + preparation and releases it on completion, startup failure or cancellation. + A stale generation fails with 412. The hook also fences applied banks by + source generation. Neither a browser-supplied filesystem path nor a second + recording is admitted. Other color modes retain the raw decoding fallback. +- `recordedPointDisplay.ts`, `useRecordedPointDisplay.ts`, RerunViewport and + viewer/recorded.py: framed RRD batches into the existing receiver, unique generation + entity below `/world/display_points`, then blueprint activation. Inactive + generations are excluded before streaming, including old cached RRDs. Unique + paths avoid accumulating old samples at the same temporal entity. Previous + representation remains visible during preparation or failure. 0% uses the + base entity; 100% uses visibility, without rebuilding an empty recording. +- The same recording, iframe, clock and native camera remain active. Blueprint + activation carries actual native eye. Changing only accumulation or size does + not regenerate points. Native SDK/WebViewer remain 0.36.3 with the previously + accepted navigation patch; this increment adds no Rust/WASM patch. + +The HTTP envelope is NPD1 + repeated little-endian u32 length / standalone RRF2 +payload + a terminal zero length. Native `send_rrd` expects a complete RRD per +call, not arbitrary HTTP chunks. Browser QA caught this distinction: an earlier +prototype appeared thinned but logged decoder errors and was rejected. The +replacement frames transport explicitly, validates every batch and refuses +activation without the success terminator. A multi-batch RrdReader regression +decodes each payload independently and counts every input frame. + +Upgrade checks: complete RRF2 batch admission, same recording ID, temporal query and +generation include/exclude precedence, point-size overrides on alternate entity, +current eye preservation, retained pause/seek and teardown/abort. Repeat profile +roundtrip and failure tests independently of browser local storage. + +## Verification and limits + +Frontend complete suite: 900 passed; production TypeScript/Vite build passed. +Final backend display/profile/colors/session/blueprint/plugin-boundary suites: +65 distinct tests passed, including atomic-write failure, source-generation +pin/release, independently decodable batches and exact prepared-RRD attributes. +Sampling tests cover 0/5/49/49.5/50/99.9/100%, exact count and nested subsets; +stream cancellation releases the preparation slot. Frontend tests cover +fragmented RRF2 headers, cancellation, cross-origin rejection, profile identity, +range override and recorded-only control exposure. + +Canonical port 8000 browser QA on corrected JA-STROITEL-SUN-RING-002: staged +95% decimation, 600 s range and 60 s current window persisted on close. The +prototype visibly thinned the cloud, but its decoder errors invalidate that as +evidence of complete delivery. Pause remained at 01:45.173 and grid/camera +framing stayed unchanged. 100% removed only points; 0% restored full cloud +without decoding it again. This tests display, not geometric accuracy or +localization. + +The earlier framed-protocol browser trials admitted multiple batches without codec +errors, but service restarts interrupted the requests before their success +terminator. The previous representation remained visible and the UI reported +the failure; it did not activate the incomplete generation. Full real-recording +completion was NOT browser-qualified at that point. +The watchdog journal has no restart-requested event for these restarts; their +initiator has not been established. No watchdog setting was weakened. The small +ring's separate replay preparation was also interrupted by a service restart. +That earlier test must not be cited as a passed large-recording test. + +After a page reload, the recording restored its exact 0.5 px point size, 0% +decimation, 600 s custom range and 60 s accumulation from the JSON profile. +The QA session was subsequently returned to 0% decimation, a 180 s range and +47 s window. The final retry-dependency fix passed production build and the nine +focused frontend profile/camera tests: unrelated size/time edits do not cancel +preparation of the same sampling key, while closing Display retries a failure. + +Follow-up browser acceptance on canonical 8000, 22 September: persisted 86.2% +was reproduced in JA-STROITEL-SUN-RING-002. The old renderer was still decoding +raw transport; retaining the old representation until completion made it look +inert. After installing the prepared-RRD path, the complete generated bank was +activated. At the same paused cursor 01:38.386 and accumulation 49 s, switching +86.2% -> 0% visibly restored the dense cloud; 100% removed points while keeping +grid/trajectory, and returning to 86.2% restored the sparse cloud. Camera framing +and the paused cursor remained unchanged. Expanded mode and host Escape return +passed. Browser error log was empty (only known native web viewport-command +warnings). The source had no decoder errors. This closes the earlier real-file +activation acceptance item. The test leaves the user's 86.2%, 0.5 px, 180 s +range and 49 s current window intact. It does not claim exact GPU counts or +physical gesture coverage. The synthetic RRD test proves 138/1000 points per +frame at 86.2%, exact XYZ/RGBA and original timestamps without raw decoding. + +OPS registry #74 is updated only after this follow-up browser qualification; +the long-lived upgrade registry itself is not moved to Done. + +Performance limitation: the old raw path took several minutes on the +24-minute/49-million-point recording. The intensity/Turbo path now traverses the +prepared RRD; other palettes/modes still use the slower raw path. A new percentage +still needs asynchronous preparation, not instantaneous GPU sampling. The server +streams bounded batches (one preparation at a time), and the browser avoids a +whole-cloud JS blob. Decimation reduces visible geometry/draw work; it does NOT +evict the base recording or guarantee lower total native store RAM. Inactive +generations remain subject to native store retention until viewer disposal. +Repeated-percentage memory stress and a persistent preview cache are not claimed +as qualified. Raw/corrected source integrity and navigation are not traded for +speed. No extra backend, Docker runtime or alternate renderer was introduced. + +Host acceptance limitation: the disk had about 300 MiB free during final QA, +below the service's 2 GiB reserve. `/api/health` reported recording-cache +capacity-pressure while the single canonical service continued serving 8000; +8765 had no listener. No recordings were deleted and the reserve was not +weakened. New materialization/load stress is not safe until disk headroom is +recovered; the prepared-RRD fix does not add a new persistent cloud cache. + +The product UI skill kept the controls in the admitted Display window and reused +canonical RangeControl/ToastStack rather than creating another workspace. + +## Follow-up: open-inspector preview regression + +The operator's 07:57 screenshots exposed a second, independent defect after the +prepared-RRD fix. `stageDisplayPatch` returned early whenever recorded playback +and Display were both open. Both the inspector and the bottom accumulation +slider used this callback: their labels showed the draft, while Rerun continued +to receive the previous scene settings. The preceding browser acceptance only +tested changes after closing Display and did not cover this interaction. + +The inspector-open suppression is removed. Preview is independent of JSON +persistence; close still saves the latest shared draft, including edits made +from the bottom slider. Cheap 0%/100% changes apply immediately. Intermediate +percentage edits remain coalesced and asynchronous, with the previous complete +bank retained until the replacement succeeds. Returning to a cached bank or +0% now clears an obsolete preparation status. No raw/corrected data, camera +navigation or native viewer build is changed by this follow-up. + +Preparation status is placed upper-right on the source-button vertical axis. +Camera windows default to bottom-left, still clear of the timeline. Explicit +saved/user-moved window rectangles retain priority over the default placement. +The product UI skill preserves the existing window and range primitives. + +Follow-up validation: architecture 4/4, full frontend 902/902, TypeScript and +production Vite build passed; `git diff --check` passed. The regression test +executes the actual App callbacks with controlled timers: open inspector, +100% -> 0%, 180 s -> 10 s, fractional 49.5% preview, then close-to-save. Existing +lower-right/left-status layout assertions were updated to the requested layout. + +Visual acceptance of this follow-up remains PENDING. The prepared large ring +loaded after the rebuild, but browser control was interrupted before the +open-inspector comparison. After the host restart the canonical LaunchAgent +failed before application startup: launchd reported `posix_spawn(uv): Operation +not permitted`, exit 78, with no listener on 8000. Kickstart and re-registration +of the identical existing plist did not recover it. No permissions, plist, +data, raw recording, or other service were changed. No alternate backend was +started. This startup failure is not proof of a Rerun failure; its exact OS-level +cause is not established. OPS is not updated for this unfinished acceptance. + +### Resumed acceptance and fractional cursor regression + +The owner requested coordination with Mission Core - SIM. That task owns the +canonical service restart; this task does not launch another backend. SIM +recovered the existing LaunchAgent by moving only its startup log out of +Downloads to the user's Library/Logs directory. Canonical health subsequently +reported operational=true, one listener on 8000, none on 8765. Browser QA ran +with one prepared recording, no build/test overlap, and 53–75% reported free +memory. Original per-session settings (86.2%, 49 s, 180 s maximum) were restored +and verified through the profile API before closing the temporary viewer for +SIM's next explicitly coordinated restart. + +Open-inspector 100% removed points and 0% restored the dense cloud; the camera +window appeared bottom-left. However, after seek and Follow on/off, 180 s and +10 s produced an unchanged view and later display requests stopped reaching the +blueprint endpoint. This is NOT accepted as working accumulation. + +An additional concrete boundary defect was reproduced in a focused test: +`fetchRecordedBlueprintRrd` rejected a native fractional-nanosecond cursor such +as 536460021972.65625 before making the request. Pinned Rerun 0.36.3 +`crates/viewer/re_viewer/src/web.rs::get_time_for_timeline` returns TimeReal as +f64, not an integer. A failed following-eye transition remains pending, so +later display-only updates retry that same invalid pose-query timestamp. +The request boundary now rounds to the nearest integer nanosecond (at most +0.5 ns); the native playback cursor is untouched. Negative, non-finite and +unsafe timestamps still fail. Aborted requests stay quiet; other blueprint +failures now produce a console warning instead of disappearing silently. +The new regression failed before the change and passed after it. Full gates +and resumed end-to-end accumulation verification are still pending; OPS remains +unchanged until they pass. + +### Final acceptance after the fractional cursor fix + +The sequential gates now pass: architecture 4/4, TypeScript, full frontend +903/903 (zero skipped), production Vite build and `git diff --check`. The build +retains only the existing large-chunk warning. Served asset +`app-Bm1TJUff.js` was confirmed before the final browser pass. No native/WASM +rebuild or further service restart was needed. + +On JA-STROITEL-SUN-RING-002, with Display open and the same paused cursor/camera, +180 s showed the longer cloud history and 10 s visibly removed older geometry. +This also passed after seeking to 01:00.780 and toggling Follow on/off. 100% +removed only points; 0% restored them. 99.9% yielded a near-empty cloud while +keeping grid/trajectory, and returning to 86.2% restored the prepared subset. +During that replacement the preparation pill was visibly upper-right, aligned +with the source/expand controls. Camera default was bottom-left. Expanded view +and Escape returned to the same paused frame and accumulation. Console error +entries and the new recorded-scene failure warnings were both empty. + +Original session profile values were restored and read back after closing +Display: 86.2% decimation, 49 s accumulation, 180 s scale maximum, 0.5 point size, +intensity/Turbo. Source recordings and sealed method.json were not rewritten. +No exact GPU point-count claim or physical trackpad-device qualification is +made by this browser pass. + +The temporary viewer was then closed rather than left resident: the explicitly +coordinated Mission Core - SIM task needed the next single-viewer acceptance +window, and the host had recently exhausted RAM. SIM was notified that the +frontend gates had already passed and must not be duplicated unnecessarily. +Final local checks: operational=true, one canonical listener on 8000, none on +8765, 62% reported free memory and about 16.5 GB available disk. No background +build, second backend or large-recording stress test was started. + +### Operator cleanup and repository packaging + +At the owner's request the persistent LMB/RMB/wheel hint was removed from the +shared SpatialScene and both recorded/live callers, with its unused CSS and +readiness prop. Native camera input and renderer code were not changed. +Architecture/layout checks 9/9, frontend 904/904, typecheck and production build +passed. Lightweight browser inspection confirmed the hint was absent; the large +recording was not reloaded solely for this copy removal after host swap growth. +The canonical service remained healthy and the temporary browser was closed. + +The accepted 0.36.3 native WASM is packaged through Git LFS, matching the existing +binary-artifact convention for the archived viewer. The source patch, paired +JS/types, licenses and hash-bound build manifest remain normal versioned files. +Raw captures, runtime state and simulation work-in-progress are outside this +publication scope. The long-lived Ops customization registry is not marked done. + +Before publication, the selected Git index was exported into an isolated +temporary source snapshot, excluding every pending SIM change (including the +SIM hunks in App, dependency manifests and web/app). Against that snapshot, +154 focused frontend tests, TypeScript typecheck and 230 focused backend tests +passed. Dependencies were reused from the existing local installation; this is +not a clean dependency-install qualification. The only backend warning was the +existing Starlette/httpx deprecation. First-party staged diff checks passed; +generated vendor JS and source-patch context retain their hash-bound whitespace. +The staged WASM is a Git LFS pointer to the manifest-verified 50,469,154-byte +artifact. Working files were unchanged by staging. No service restart, new +physical scan, repeat full build or large-recording replay was needed for this +publication gate; canonical 8000 returned operational=true. diff --git a/experiments/activate_session_map.py b/experiments/activate_session_map.py new file mode 100644 index 0000000..733f8be --- /dev/null +++ b/experiments/activate_session_map.py @@ -0,0 +1,33 @@ +"""Admit a reviewed full map as the physical session's operator/LAB default. + +Explicit offline maintenance command; no solver, no device commands, no new +catalog session and no change to raw capture or already pinned studies. +""" + +import argparse +from pathlib import Path + +from package_recorded_map_version import RecordedParent + +from k1link.reconstruction.map_version import MapVersion +from k1link.reconstruction.session_versions import SessionMapVersions + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--version", type=Path, required=True) + parser.add_argument("--raw", type=Path, required=True) + parser.add_argument("--data-dir", type=Path, required=True) + args = parser.parse_args() + version = MapVersion(args.version, args.version.name) + parent = version.document["source"] + original = RecordedParent(args.raw, parent["session_id"], parent["generation"]) + admitted = SessionMapVersions(args.data_dir).activate(version, original) + print( + f"Session: {parent['session_id']}\nDefault map: {admitted.generation}\n" + "Uses: recorded playback, laboratory reference. Vehicle control: false." + ) + + +if __name__ == "__main__": + main() diff --git a/experiments/audit_recorded_cloud_fidelity.py b/experiments/audit_recorded_cloud_fidelity.py new file mode 100644 index 0000000..eced17a --- /dev/null +++ b/experiments/audit_recorded_cloud_fidelity.py @@ -0,0 +1,40 @@ +"""Read-only, chunk-bounded check of the actual point rows in a derived RRD.""" + +import argparse +import json +from pathlib import Path + +import pyarrow.compute as pc +from rerun.experimental import RrdReader + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("recording", type=Path) + parser.add_argument("--expected-frames", type=int, required=True) + parser.add_argument("--expected-points", type=int, required=True) + args = parser.parse_args() + frames = points = 0 + for chunk in RrdReader(args.recording).stream(): + if chunk.entity_path != "/world/points": + continue + batch = chunk.to_record_batch() + if "Points3D:positions" not in batch.schema.names: + continue + positions = batch.column("Points3D:positions") + frames += len(positions) - positions.null_count + points += pc.sum(pc.list_value_length(positions)).as_py() or 0 + result = { + "recording": str(args.recording), + "point_frames": frames, + "points": points, + "expected_point_frames": args.expected_frames, + "expected_points": args.expected_points, + "passed": frames == args.expected_frames and points == args.expected_points, + } + print(json.dumps(result, indent=2)) + return 0 if result["passed"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/package_recorded_map_version.py b/experiments/package_recorded_map_version.py new file mode 100644 index 0000000..8e4ad8a --- /dev/null +++ b/experiments/package_recorded_map_version.py @@ -0,0 +1,310 @@ +"""Package the reviewed ring derivative for explicit offline planning consumption. + +Reads the canonical source API, but never writes to it or imports the web app. +Verifies the raw transport, clocks, reviewed code/artifacts and all corrected +frames against the frozen correction field before publishing a separate bundle. +No refit, capture, reference switch, source-catalog entry or vehicle authority. +""" + +from __future__ import annotations + +import argparse +import json +import time +from pathlib import Path +from tempfile import TemporaryDirectory +from urllib.parse import quote +from urllib.request import urlopen + +import numpy as np + +from k1link.missions.versioned_sources import VersionedPlanningSources +from k1link.reconstruction.map_version import publish_map_version, sha256 +from k1link.reconstruction.smooth_correction import CorrectionField + + +class RecordedParent: + """A read-only generation-bound parent; all native clock artifacts are checked.""" + + def __init__(self, raw, session_id, generation): + self.raw, self.session_id, self.generation = raw, session_id, generation + + def get(self, session_id): + if session_id != self.session_id: + raise ValueError("Unexpected source session.") + url = ( + "http://127.0.0.1:8000/api/v1/mission-planner/sources/" + + quote(session_id, safe="") + + "?generation=" + + quote(self.generation, safe="") + ) + with urlopen(url, timeout=30) as response: + source = json.load(response) + if source["generation"] != self.generation: + raise ValueError("Original source generation changed.") + return source + + def verify(self, session_id, generation): + if generation != self.generation: + raise ValueError("Original source generation changed.") + source = self.get(session_id) + # Plugin-specific recorded-ring adapter, not a generic Core discovery rule. + expected = source["source_digests"] + if expected.get("raw-transport-primary") != sha256(self.raw): + raise ValueError("Original transport identity changed.") + index = self.raw.with_name("mqtt.metadata.jsonl") + origin = self.raw.with_name("mqtt.timeline.origin.json") + if sha256(index) != expected.get("raw-transport-index") or sha256(origin) != expected.get( + "raw-transport-clock-origin" + ): + raise ValueError("Original receipt clocks changed.") + clocks = [ + self.raw.with_name("mqtt.timeline.json"), + self.raw.with_name( + "mqtt.timeline.session-" + expected["raw-transport-clock"] + ".json" + ), + ] + if not any( + p.is_file() and sha256(p) == expected.get("raw-transport-clock") for p in clocks + ): + raise ValueError("Original capture clock changed.") + if self.get(session_id) != source: + raise ValueError("Original source changed during verification.") + return source + + bound = verify + + +def check_closure_review(result, summary): + """New producers must carry a complete acquisition and a positive frozen review. + + Retain explicit v1 compatibility for previously reviewed/pinned bundles. + A missing v2 review cannot silently fall back to the legacy contract. + """ + schema = summary.get("schema_version") + if schema == "missioncore.recorded-ring-experiment/v1": + return [] + if schema != "missioncore.recorded-ring-experiment/v2": + raise ValueError("Unsupported closure producer contract.") + search = json.loads((result / "closure-search.json").read_text()) + review = json.loads((result / "review.json").read_text()) + acceptance = review.get("acceptance", {}) + required = { + "all_local_windows_qualified", + "heldout_seam_present", + "heldout_seam_quality", + "heldout_seam_not_degraded", + } + if ( + search.get("status") != "candidate" + or search.get("complete") is not True + or len(search.get("attempts", [])) != search.get("expected_attempts") + or acceptance.get("schema_version") != "missioncore.closure-review/v1" + or acceptance.get("accepted") is not True + or set(acceptance.get("checks", {})) != required + or any(acceptance["checks"][key] is not True for key in required) + or summary.get("closure_acquisition") != "candidate" + ): + raise ValueError("Closure acquisition or held-out review is not qualified.") + return ["closure-search.json"] + + +def prepare(args): + started = time.monotonic() + result = args.result.resolve() + seal_path = result / "review.json.seal.json" + if sha256(seal_path) != args.review_seal_sha256: + raise ValueError("Reviewed evidence seal identity differs.") + sealed = json.loads(seal_path.read_text()) + for path, expected in sealed.items(): + if sha256(Path(path)) != expected: + raise ValueError("Reviewed artifact or producer changed: " + Path(path).name) + names = [ + "summary.json", + "correction.json", + "validation.json", + "registrations.json", + "surface-links.json", + "review.json", + "corrected-points.f32", + "corrected-trajectory.npz", + ] + summary = json.loads((result / "summary.json").read_text()) + additional_evidence = check_closure_review(result, summary) + if any(str(result / name) not in sealed for name in names + additional_evidence): + raise ValueError("Review seal does not bind the complete candidate.") + if ( + summary["status"] != "experimental-candidate-not-promoted" + or summary["production_promotion"] + or summary["vehicle_control"] + ): + raise ValueError("Unsupported experiment contract or authority.") + parent = RecordedParent(args.raw, summary["source_session"], args.source_generation) + source = parent.verify(summary["source_session"], args.source_generation) + if sha256(args.raw) != summary["source"]["source_sha256"]: + raise ValueError("Experiment belongs to another physical recording.") + correction = json.loads((result / "correction.json").read_text()) + if ( + correction["schema_version"] != "missioncore.smooth-map-correction/v2" + or not correction["converged"] + ): + raise ValueError("A converged, reviewed v2 correction is required.") + field = CorrectionField(correction["knots_m"], correction["parameters"], correction["origin_m"]) + cache = args.cache.resolve() + cache_seal = json.loads((cache / "seal.json").read_text()) + for name in ("source-points.f32", "source-intensity.u8", "index.npz", "source.json"): + if sha256(cache / name) != cache_seal[name]: + raise ValueError("Decoded source cache changed.") + if json.loads((cache / "source.json").read_text()) != summary["source"]: + raise ValueError("Decoded cache belongs to another experiment source.") + with np.load(cache / "index.npz", allow_pickle=False) as original: + poses, frames = original["poses"], original["frames"] + distances, frame_distances = original["distance"], original["frame_distance"] + with np.load(result / "corrected-trajectory.npz", allow_pickle=False) as data: + trajectory = {k: data[k] for k in data.files} + if ( + len(poses) != len(source["poses"]) + or not np.allclose( + poses[:, 1:4], [p["position"] for p in source["poses"]], atol=1e-10, rtol=0 + ) + or not np.allclose( + poses[:, 0] - poses[0, 0], [p["elapsed_s"] for p in source["poses"]], atol=1e-6, rtol=0 + ) + ): + raise ValueError("Source trajectory or clock ownership differs from the reviewed cache.") + positions, orientations = field.poses(poses[:, 1:4], poses[:, 4:8], distances) + if ( + not np.allclose(positions, trajectory["positions"], atol=1e-9, rtol=0) + or not np.allclose(orientations, trajectory["orientations_xyzw"], atol=1e-9, rtol=0) + or not np.array_equal(frames, trajectory["frames"]) + or not np.array_equal(poses[:, 0], trajectory["receipt_time_s"]) + or not np.array_equal(distances, trajectory["distance_m"]) + or not np.array_equal(frame_distances, trajectory["frame_distance_m"]) + ): + raise ValueError("Corrected poses/frames do not reproduce the reviewed field.") + maximum_error = 0.0 + with ( + (cache / "source-points.f32").open("rb") as raw_points, + (result / "corrected-points.f32").open("rb") as corrected, + ): + for frame, distance in zip(frames, frame_distances, strict=True): + count = int(frame[3]) + original = np.frombuffer(raw_points.read(count * 12), dtype="= PROFILE["holdout_start_s"]) & ( + phase < PROFILE["holdout_start_s"] + PROFILE["holdout_duration_s"] + ) + nearest = np.clip(np.searchsorted(p[:, 0], f[:, 0]), 1, len(p) - 1) + gap = np.minimum(abs(p[nearest, 0] - f[:, 0]), abs(p[nearest - 1, 0] - f[:, 0])) + if digest(raw) != expected: + raise ValueError("Source changed while decoding; derivative is not admissible.") + np.savez( + output / "index.npz", + poses=p, + frames=f, + distance=distance, + frame_distance=frame_distance, + heldout=held, + sample_points=np.concatenate(samples), + sample_frame=np.concatenate(sample_ids), + ) + meta = dict( + source_sha256=expected, + profile=PROFILE, + frames=len(f), + poses=len(p), + points=total, + seconds=time.monotonic() - started, + path_m=float(distance[-1]), + receipt_pose_nearest_gap_p95_s=float(np.quantile(gap, 0.95)), + receipt_pose_nearest_gap_max_s=float(gap.max()), + clock_binding="host-monotonic interpolation; NOT hardware synchronization", + mapped_increment_not_native_sweep=True, + heldout_frames=int(held.sum()), + training_frames=int((~held).sum()), + ) + write_json(output / "source.json", meta) + return meta + + +def voxel(points): + if not len(points): + return points + _, idx = np.unique( + np.floor(points / PROFILE["voxel_m"]).astype(np.int64), axis=0, return_index=True + ) + return points[np.sort(idx)] + + +def register(reference, query, seed, *, acquisition=False): + try: + result = PreparedReference(reference).register( + query, seed, policy=CLOSURE_REGISTRATION_POLICY if acquisition else POLICY + ) + result.pop("matched_query_indices", None) + return result + except ValueError as exc: + return dict(status="unavailable", reasons=[str(exc)]) + + +def links_for(data): + p, s = data["poses"], data["frame_distance"] + pts, ids, held = data["sample_points"], data["sample_frame"], data["heldout"] + training = ~held[ids] + + def take(frame_mask, center, radius): + mask = training & frame_mask[ids] + chunk = pts[mask] + return chunk[np.linalg.norm(chunk - center, axis=1) <= radius] + + link, search = acquire_closure(data, register) + selected = search["attempts"][search["selected_attempt"]] + links = [link] + audits = [dict(kind="seam", **selected, search=search)] + # Disjoint time/distance windows: no shared frame can self-match across an edge. + centers = np.linspace(0, data["distance"][-1], int(np.ceil(data["distance"][-1] / 20)) + 1) + for i, (sa, sb) in enumerate(zip(centers[:-1], centers[1:], strict=True)): + width = (sb - sa) / 3 + amask, bmask = abs(s - sa) <= width, abs(s - sb) <= width + assert not np.any(amask & bmask) + pivot = np.array([np.interp((sa + sb) / 2, data["distance"], p[:, j]) for j in range(1, 4)]) + a = take(amask, pivot, PROFILE["neighbor_radius_m"]) + b = take(bmask, pivot, PROFILE["neighbor_radius_m"]) + fit = register(a, b, np.eye(4)) + audits.append(dict(kind="neighbor", distances_m=[float(sa), float(sb)], fit=fit)) + if fit["status"] == "candidate": + links.append( + SurfaceLink( + float(np.mean(s[amask & ~held])), + float(np.mean(s[bmask & ~held])), + np.asarray(fit["T_reference_query"]), + np.median(b, axis=0), + PROFILE["neighbor_translation_weight_m"], + PROFILE["neighbor_rotation_weight_deg"], + f"neighbor-{i}", + ) + ) + print(f"neighbor {i + 1}/{len(centers) - 1}: {fit['status']}", flush=True) + return links, audits + + +def evaluate(data, field, label): + pts, ids = data["sample_points"], data["sample_frame"] + s, f, p = data["frame_distance"], data["frames"], data["poses"] + train = ~data["heldout"][ids] + corrected = np.empty_like(pts) + offsets = np.searchsorted(ids, np.arange(len(f) + 1)) + for i, distance in enumerate(s): + start, end = offsets[i : i + 2] + corrected[start:end] = field.points(pts[start:end], distance) + target = voxel(corrected[train]) + tree = cKDTree(target) + groups = np.floor((f[:, 0] - f[0, 0]) / PROFILE["holdout_period_s"]).astype(int) + seed, rows = np.eye(4), [] + for group in np.unique(groups[data["heldout"]]): + frames = data["heldout"] & (groups == group) + seconds, distance = float(np.mean(f[frames, 0])), float(np.mean(s[frames])) + position = np.array([np.interp(seconds, p[:, 0], p[:, j]) for j in range(1, 4)]) + query = pts[frames[ids]] # Uncorrected, held-out scanner output. + radius = PROFILE["local_validation_radius_m"] + query = query[np.linalg.norm(query - position, axis=1) <= radius] + estimated = transform(position[None], seed)[0] + reference = target[tree.query_ball_point(estimated, radius + 5)] + fit = register(reference, query, seed) + row = dict( + group=int(group), + distance_m=distance, + source_frames=int(frames.sum()), + source_query_points=len(query), + fit=fit, + ) + if "T_reference_query" in fit: + fitted = np.asarray(fit["T_reference_query"]) + implied = field.matrices(distance)[0] + row["model_consistency_m"] = float( + np.linalg.norm( + transform(position[None], fitted) - transform(position[None], implied) + ) + ) + row["model_consistency_deg"] = float( + np.rad2deg( + np.linalg.norm( + Rotation.from_matrix(fitted[:3, :3].T @ implied[:3, :3]).as_rotvec() + ) + ) + ) + # All-point tails remain visible, not just accepted correspondences. + dist, _ = tree.query(transform(query, fitted), workers=1) + row["all_point_distance_p95_m"] = float(np.quantile(dist, 0.95)) + if fit["status"] == "candidate": + seed = fitted # causal last accepted transform, never field oracle. + rows.append(row) + if len(rows) % 10 == 0: + print(f"validation {label}: {len(rows)} windows", flush=True) + return dict( + label=label, + training_map_points=len(target), + windows=rows, + candidate_count=sum(r["fit"]["status"] == "candidate" for r in rows), + total=len(rows), + interpretation="same-source held-out-frame local matching, not independent truth", + seed="identity then previous accepted transform; no current-field seed", + ) + + +def materialize(cache, data, field, output): + frames = data["frames"] + count = int(frames[-1, 2] + frames[-1, 3]) + source = np.memmap(cache / "source-points.f32", dtype="= f[-1, 0] - 15) & held + # Identical source point membership before/after, selected before correction. + near = np.linalg.norm(pts - p[0, 1:4], axis=1) <= 25 + first_pts = transformed[first[ids] & near] + last_pts = transformed[last[ids] & near] + distances, _ = cKDTree(voxel(first_pts)).query(last_pts, workers=1) + seam = dict( + query_frames=int(last.sum()), + points=len(last_pts), + overlap_05m=float(np.mean(distances <= 0.5)), + all_point_distances_m=stats(distances), + inlier_rmse_m=float(np.sqrt(np.mean(distances[distances <= 0.5] ** 2))), + refit=False, + ) + focused = [] + for group in failures: + # Same prior as the original two-second query; then only fresh held-out halves. + full = next(r for r in run["windows"] if r["group"] == group) + prior = np.asarray(full["fit"]["initial_T_reference_query"]) + t0 = f[0, 0] + group * PROFILE["holdout_period_s"] + PROFILE["holdout_start_s"] + for half in [0, 1]: + mask = held & (f[:, 0] >= t0 + half) & (f[:, 0] < t0 + half + 1) + position = np.array( + [np.interp(float(np.mean(f[mask, 0])), p[:, 0], p[:, j]) for j in range(1, 4)] + ) + query = pts[mask[ids]] + query = query[np.linalg.norm(query - position, axis=1) <= 40] + estimated = transform(position[None], prior)[0] + reference = target[tree.query_ball_point(estimated, 45)] + match = register(reference, query, prior) + focused.append(dict(group=group, half=half, frames=int(mask.sum()), fit=match)) + if match["status"] == "candidate": + prior = np.asarray(match["T_reference_query"]) + results.append( + dict( + label=label, + seam_holdout=seam, + focused=focused, + overlap=stats([r["fit"]["overlap"] for r in run["windows"]]), + inlier_rmse_m=stats([r["fit"]["inlier_rmse_m"] for r in run["windows"]]), + model_consistency_m=stats([r["model_consistency_m"] for r in run["windows"]]), + ) + ) + acceptance = review_acceptance(results, runs) + write_json( + root / args.output_name, + dict( + frozen_correction=True, + unchanged_registration_policy=True, + selected_failure_groups=failures, + results=results, + acceptance=acceptance, + ), + ) + sources = [ + Path(__file__), + Path(__file__).with_name("reconstruct_recorded_ring.py"), + Path(__file__).parents[1] / "src/k1link/reconstruction/smooth_correction.py", + Path(__file__).parents[1] / "src/k1link/reconstruction/closure.py", + Path(__file__).parents[1] / "src/k1link/missions/registration.py", + ] + evidence = [ + root / name + for name in [ + "summary.json", + "correction.json", + "validation.json", + "registrations.json", + "surface-links.json", + "corrected-points.f32", + "corrected-trajectory.npz", + args.output_name, + ] + ] + if summary["schema_version"] == "missioncore.recorded-ring-experiment/v2": + evidence.append(root / "closure-search.json") + write_json( + root / (args.output_name + ".seal.json"), + {str(path.resolve()): digest(path) for path in sources + evidence}, + ) + print(json.dumps(results, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/packages/spatial-ui/src/FloatingMediaWindow.tsx b/packages/spatial-ui/src/FloatingMediaWindow.tsx index 85d8403..c4248e1 100644 --- a/packages/spatial-ui/src/FloatingMediaWindow.tsx +++ b/packages/spatial-ui/src/FloatingMediaWindow.tsx @@ -43,8 +43,7 @@ export function initialObservationWindowRect( const row = Math.floor(index / columns); const column = index % columns; const rowItemCount = Math.min(columns, Math.max(1, count - row * columns)); - const rowWidth = rowItemCount * width + Math.max(0, rowItemCount - 1) * WINDOW_GAP; - const rowStart = Math.max(0, bounds.width - WINDOW_INSET - rowWidth); + const rowStart = Math.min(WINDOW_INSET, Math.max(0, bounds.width - width)); return { x: rowStart + Math.min(column, rowItemCount - 1) * (width + WINDOW_GAP), diff --git a/packages/spatial-ui/src/ObservationTimeline.tsx b/packages/spatial-ui/src/ObservationTimeline.tsx index 1def81b..94772c9 100644 --- a/packages/spatial-ui/src/ObservationTimeline.tsx +++ b/packages/spatial-ui/src/ObservationTimeline.tsx @@ -1,4 +1,5 @@ import { Button, Icon, Select } from "@nodedc/ui-react"; +import { MAX_ACCUMULATION_SECONDS } from "./sceneSettings"; type ObservationTimelineMode = "live-only" | "buffered" | "recorded"; @@ -18,6 +19,7 @@ export function ObservationTimeline({ playbackRate, onPlaybackRateChange, accumulationSeconds, + accumulationMaxSeconds = MAX_ACCUMULATION_SECONDS, onAccumulationChange, onAccumulationCommit, className = "", @@ -37,6 +39,7 @@ export function ObservationTimeline({ playbackRate?: number; onPlaybackRateChange?: (rate: number) => void; accumulationSeconds?: number; + accumulationMaxSeconds?: number; onAccumulationChange?: (value: number) => void; onAccumulationCommit?: () => void; className?: string; @@ -75,7 +78,7 @@ export function ObservationTimeline({ className="observation-timeline__track" type="range" min={0} - max={120} + max={Math.max(1, accumulationMaxSeconds, accumulationValue)} step={1} value={accumulationValue} aria-label="Окно накопления облака точек" @@ -165,12 +168,15 @@ export function ObservationTimeline({ export function normalizeAccumulationSeconds(value: number): number { if (!Number.isFinite(value)) return 0; - return Math.min(120, Math.max(0, Math.round(value))); + return Math.max(0, Math.round(value)); } export function formatAccumulationDuration(value: number): string { const seconds = normalizeAccumulationSeconds(value); - return seconds === 0 ? "Кадр" : `${seconds} с`; + if (seconds === 0) return "Кадр"; + if (seconds < 60) return `${seconds} с`; + const minutes = Math.floor(seconds / 60); + return seconds % 60 === 0 ? `${minutes} мин` : `${minutes} мин ${seconds % 60} с`; } export function normalizeTimelineRange( diff --git a/packages/spatial-ui/src/SceneDisplayControls.tsx b/packages/spatial-ui/src/SceneDisplayControls.tsx index 7a59f6b..c0f7b43 100644 --- a/packages/spatial-ui/src/SceneDisplayControls.tsx +++ b/packages/spatial-ui/src/SceneDisplayControls.tsx @@ -1,5 +1,7 @@ import {Checker,ColorField,ControlRow,Inspector,RangeControl,Select} from '@nodedc/ui-react'; import type {SceneSettings,PointColorMode,PointPalette} from './sceneSettings'; +import {MAX_ACCUMULATION_SECONDS} from './sceneSettings'; +import {formatAccumulationDuration} from './ObservationTimeline'; const colorModeOptions: Array<{ value: PointColorMode; label: string; description: string }> = [ { value: "intensity", label: "Интенсивность", description: "Значение отражённого сигнала" }, @@ -28,7 +30,7 @@ export function SceneDisplayControls({displayDraft,stageDisplayPatch,commitDispl { id: "points", label: "Облако точек", - description: "Размер и способ окрашивания", + description: "Размер, плотность и окрашивание", content: (
stageDisplayPatch({ pointSize })} />
+ {replayPresented ? <> + `${value.toLocaleString('ru-RU')} %`} + onChange={(pointDecimationPercent) => stageDisplayPatch({pointDecimationPercent})} + /> +

0% — все точки, 100% — без точек. Предпросмотр обновляется после изменения. Настройки сохраняются при закрытии окна; исходная запись не меняется.

+ : null}