refactor(lab): перевести RAV004 на канонический Rerun pipeline

This commit is contained in:
DCCONSTRUCTIONS
2026-08-30 12:59:16 +03:00
parent e9ffb829c9
commit f5ee42751d
30 changed files with 1425 additions and 288 deletions
+4 -5
View File
@@ -7,7 +7,6 @@
"": {
"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",
@@ -15,7 +14,7 @@
"@nodedc/tokens": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/tokens",
"@nodedc/ui-core": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/ui-core",
"@nodedc/ui-react": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/ui-react",
"@rerun-io/web-viewer": "0.34.1",
"@rerun-io/web-viewer": "0.36.3",
"meshoptimizer": "1.1.1",
"playcanvas": "2.21.4",
"react": "^19.1.0",
@@ -900,9 +899,9 @@
"link": true
},
"node_modules/@rerun-io/web-viewer": {
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/@rerun-io/web-viewer/-/web-viewer-0.34.1.tgz",
"integrity": "sha512-2Oq9Mw3qOs765XArGq4e/0pAIksPFKlAqkgo+PDsRkPd77/KdnQhb835suRmYQ6tuqlbPeVCFhlNIXT0+TjmTg==",
"version": "0.36.3",
"resolved": "https://registry.npmjs.org/@rerun-io/web-viewer/-/web-viewer-0.36.3.tgz",
"integrity": "sha512-LMGnsxRmY5UwiGras2dZrMnEYkow5Xr4v+1hAUSspXWPPiilMqoz9G77jo8Ps/deAaX81TnOq123DFO8iX/Ulw==",
"license": "MIT"
},
"node_modules/@rolldown/pluginutils": {
+1 -2
View File
@@ -4,7 +4,6 @@
"private": true,
"type": "module",
"scripts": {
"postinstall": "node scripts/patch-rerun-web-viewer.mjs",
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
@@ -18,7 +17,7 @@
"@nodedc/tokens": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/tokens",
"@nodedc/ui-core": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/ui-core",
"@nodedc/ui-react": "file:../../../NODEDC_DESIGN_GUIDELINE/packages/ui-react",
"@rerun-io/web-viewer": "0.34.1",
"@rerun-io/web-viewer": "0.36.3",
"meshoptimizer": "1.1.1",
"playcanvas": "2.21.4",
"react": "^19.1.0",
@@ -151,6 +151,7 @@ interface RecordedRerunIdentity {
const RECORDED_RRD_PATH = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/recording\.rrd$/;
const RECORDED_BLUEPRINT_PATH = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/blueprint\.rrd$/;
const RECORDED_PERCEPTION_PATH = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/perception\.rrd$/;
const LAB_RECORDED_PERCEPTION_PATH = /^\/api\/v1\/laboratory\/vegetation-shadow\/lab-v1-vegetation-shadow-[a-f0-9]{64}\/canonical-overlay\.rrd$/;
const RECORDED_POINT_COLORS_PATH = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/point-colors\.rrd$/;
const MAX_BLUEPRINT_BYTES = 1_048_576;
const MAX_PERCEPTION_BYTES = 512 * 1024 * 1024;
@@ -203,12 +204,7 @@ export function resolveRerunSourceUrl(sourceUrl: string, origin: string): string
return new URL(normalized, `${base.origin}/`).href;
}
/**
* The native Rerun HTTP receiver is the only browser API in 0.34.1 with a
* persistent incremental RRD decoder. Bind it to the exact immutable
* generation selected by the launch contract; arbitrary `send_rrd` byte
* slices are independently decoded files and are therefore invalid.
*/
/** Bind the upstream Rerun HTTP receiver to one immutable source generation. */
export function resolveRecordedViewerSourceUrl(
descriptor: RecordedRrdArtifactDescriptor,
origin: string,
@@ -238,12 +234,6 @@ export function rerunViewerInitialSource(
return resolvedSourceUrl;
}
export function rerunViewerOpenOptions(
followLive: boolean,
): { follow_if_http: true } | null {
return followLive ? { follow_if_http: true } : null;
}
export function resolveRecordedBlueprintUrl(sourceUrl: string, origin: string): string | null {
const normalized = sourceUrl.trim();
if (!RECORDED_RRD_PATH.test(normalized)) return null;
@@ -365,6 +355,9 @@ export async function fetchRecordedBlueprintRrd(
activeView = "spatial",
viewResetGeneration = 0,
followTrajectory = false,
semanticLayer,
unifiedPerception,
planView = false,
perceptionLayers = {
enabled: false,
detections2d: false,
@@ -379,10 +372,15 @@ export async function fetchRecordedBlueprintRrd(
activeView?: RecordedRerunView;
viewResetGeneration?: 0 | 1;
followTrajectory?: boolean;
semanticLayer?: "city" | "vegetation";
unifiedPerception?: boolean;
planView?: boolean;
perceptionLayers?: RecordedPerceptionLayers;
fetcher?: typeof globalThis.fetch;
},
): Promise<Uint8Array> {
const resolvedUnifiedPerception =
unifiedPerception ?? (perceptionLayers.enabled && activeView !== "spatial");
const base = new URL(origin);
const endpoint = new URL(endpointUrl, base.origin);
if (
@@ -401,6 +399,9 @@ export async function fetchRecordedBlueprintRrd(
!/^#[0-9A-Fa-f]{6}$/.test(settings.customColor) ||
!["spatial", "perception", "perception3d", "metrics"].includes(activeView) ||
![0, 1].includes(viewResetGeneration) ||
(semanticLayer !== undefined && !["city", "vegetation"].includes(semanticLayer)) ||
typeof resolvedUnifiedPerception !== "boolean" ||
typeof planView !== "boolean" ||
[
perceptionLayers.enabled,
perceptionLayers.detections2d,
@@ -435,8 +436,9 @@ export async function fetchRecordedBlueprintRrd(
active_view: activeView,
view_reset_generation: viewResetGeneration,
follow_trajectory: followTrajectory,
unified_perception:
perceptionLayers.detections2d || perceptionLayers.segmentation,
unified_perception: resolvedUnifiedPerception,
semantic_layer: semanticLayer ?? null,
plan_view: planView,
show_detections_2d: perceptionLayers.detections2d,
show_segmentation: perceptionLayers.segmentation,
show_cuboids_3d: perceptionLayers.cuboids3d,
@@ -487,7 +489,8 @@ export async function fetchRecordedPerceptionRrd(
endpoint.origin !== base.origin ||
endpoint.search ||
endpoint.hash ||
!RECORDED_PERCEPTION_PATH.test(endpoint.pathname) ||
!(RECORDED_PERCEPTION_PATH.test(endpoint.pathname) ||
LAB_RECORDED_PERCEPTION_PATH.test(endpoint.pathname)) ||
identity.applicationId !== "nodedc_mission_core_recorded" ||
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(identity.recordingId)
) {
@@ -588,6 +591,10 @@ export function RerunViewport({
segmentation: false,
cuboids3d: false,
};
const recordedPerceptionSourceUrl = recordedProfile?.perceptionSourceUrl;
const recordedSemanticLayer = recordedProfile?.semanticLayer;
const recordedUnifiedPerception = recordedProfile?.unifiedPerception ?? false;
const recordedPlanView = recordedProfile?.planView ?? false;
const recordedPerceptionRetryGeneration =
recordedProfile?.perceptionRetryGeneration ?? 0;
const lockPerceptionCameraInteraction =
@@ -621,9 +628,11 @@ export function RerunViewport({
const recordedBlueprintUrl = sourceUrl
? resolveRecordedBlueprintUrl(sourceUrl, window.location.origin)
: null;
const recordedPerceptionUrl = sourceUrl
? resolveRecordedPerceptionUrl(sourceUrl, window.location.origin)
: null;
const recordedPerceptionUrl = recordedPerceptionSourceUrl
? resolveRerunSourceUrl(recordedPerceptionSourceUrl, window.location.origin)
: sourceUrl
? resolveRecordedPerceptionUrl(sourceUrl, window.location.origin)
: null;
const recordedPointColorsUrl = sourceUrl
? resolveRecordedPointColorsUrl(sourceUrl, window.location.origin)
: null;
@@ -1264,7 +1273,7 @@ export function RerunViewport({
try {
const recordingId = viewer.get_active_recording_id();
if (!recordingId) return;
// Rerun 0.34.1 may ingest an SDK gRPC store without forwarding its
// A live SDK receiver may ingest a store without forwarding its
// recording_open event to the JavaScript wrapper. The active store
// is the authoritative fallback and avoids hiding a ready canvas.
admitRecording({
@@ -1311,11 +1320,8 @@ export function RerunViewport({
);
try {
// `panel_state_overrides` is part of Rerun's runtime AppOptions but
// omitted from the public WebViewerOptions declaration in 0.34.1.
// Its generated TypeScript declaration says `hidden`, while the
// WASM constructor actually deserializes the Rust enum `Hidden`.
// Keep the post-start overrides below as a compatibility fallback.
// Keep all application chrome in the Mission Core shell. Rerun owns
// the synchronized canvas, data store and clock, not another window.
const viewerOptions = {
width: "100%",
height: "100%",
@@ -1336,7 +1342,6 @@ export function RerunViewport({
rerunViewerInitialSource(resolvedSource),
host,
viewerOptions,
rerunViewerOpenOptions(followLive),
);
if (disposed) {
disposeViewer();
@@ -1429,6 +1434,7 @@ export function RerunViewport({
recordedArtifact?.sha256,
recordedArtifact?.sourceUrl,
recordedArtifact?.viewerSourceUrl,
recordedPerceptionUrl,
retryNonce,
]);
@@ -1484,6 +1490,7 @@ export function RerunViewport({
message: "Сервер готовит AI-слои.",
});
const pollPreparationStatus = () => {
if (!RECORDED_PERCEPTION_PATH.test(new URL(recordedPerceptionUrl).pathname)) return;
void fetchPerceptionPreparationStatus(
recordedPerceptionUrl,
identity.recordingId,
@@ -1704,6 +1711,9 @@ export function RerunViewport({
viewResetGeneration: recordedViewResetGeneration,
followTrajectory: recordedFollowTrajectory,
perceptionLayers: recordedPerceptionLayers,
semanticLayer: recordedSemanticLayer,
unifiedPerception: recordedUnifiedPerception,
planView: recordedPlanView,
}).then((payload) => {
if (
abort.signal.aborted ||
@@ -1725,6 +1735,9 @@ export function RerunViewport({
recordedView,
recordedViewResetGeneration,
recordedFollowTrajectory,
recordedSemanticLayer,
recordedUnifiedPerception,
recordedPlanView,
recordedPerceptionLayers.enabled,
recordedPerceptionLayers.detections2d,
recordedPerceptionLayers.segmentation,
@@ -9,6 +9,7 @@ export const CANONICAL_RECORDED_LAB_REPLAY_CONTRACT =
export interface CanonicalRecordedLabMode<T extends string> {
value: T;
label: string;
disabled?: boolean;
}
/**
@@ -90,6 +91,7 @@ export function CanonicalRecordedLabReplay<
mediaMultiLayer = false,
mediaContent,
spatialContent,
unifiedContent,
emptyMessage,
deckOverlays,
actions,
@@ -115,8 +117,10 @@ export function CanonicalRecordedLabReplay<
spatialLayerControls?: ReactNode;
spatialLeadingControl?: ReactNode;
mediaMultiLayer?: boolean;
mediaContent: ReactNode;
spatialContent: ReactNode;
mediaContent?: ReactNode;
spatialContent?: ReactNode;
/** One upstream Rerun viewer owns both panes and the shared playback clock. */
unifiedContent?: ReactNode;
emptyMessage: string;
deckOverlays?: ReactNode;
actions?: ReactNode;
@@ -220,7 +224,13 @@ export function CanonicalRecordedLabReplay<
className="m4-replay-threat-visual__deck"
data-split={splitView ? "true" : undefined}
data-empty={mediaMode === "none" && spatialMode === "none" ? "true" : undefined}
data-native-rerun={unifiedContent ? "true" : undefined}
>
{unifiedContent ? (
<div className="m4-replay-threat-visual__unified-content">
{unifiedContent}
</div>
) : null}
<SplitPane
primary={mediaPane}
secondary={spatialPane ?? <div />}
@@ -37,7 +37,7 @@ export function laboratoryRecordedEvidenceDemand(
profile: LaboratoryRecordedEvidenceViewerProfile =
LABORATORY_RECORDED_EVIDENCE_VIEWER_PROFILE,
): LaboratoryRecordedEvidenceDemand {
if (profile.loadPolicy !== "visible-evidence-only") {
if (profile.loadPolicy !== "explicit-legacy-comparison-only") {
throw new Error("Unsupported LAB recorded evidence load policy");
}
const mediaVisible = visibility.mediaMode !== null;
@@ -67,20 +67,29 @@ export interface RecordedSessionRerunProfile {
viewResetGeneration: 0 | 1;
followTrajectory: boolean;
perceptionLayers: RecordedPerceptionLayers;
/** Optional immutable RRD sidecar for LAB/model evidence on the same recording clock. */
perceptionSourceUrl?: string;
/** Selects one semantic entity without changing the sealed sidecar. */
semanticLayer?: "city" | "vegetation";
/** Keeps one native Rerun store/viewer while presenting the accepted two-pane LAB layout. */
unifiedPerception?: boolean;
/** Requests the canonical top-down eye without changing the world coordinate frame. */
planView?: boolean;
perceptionRetryGeneration: number;
lockPerceptionCameraInteraction: boolean;
}
/**
* LAB recorded evidence is not a native Rerun mode. It owns a source-sequence
* clock and composes the existing sealed fMP4 and retained spatial primitives.
* Deprecated comparison-only contract for LAB artifacts that have not yet
* been republished as a native Rerun sidecar. It must never be selected by a
* canonical LAB route or start work in the background.
*/
export interface LaboratoryRecordedEvidenceViewerProfile {
kind: "lab-recorded-evidence";
clock: "source-sequence";
cameraTransport: "generation-bound-fmp4";
spatialTransport: "bounded-sealed-artifacts";
loadPolicy: "visible-evidence-only";
loadPolicy: "explicit-legacy-comparison-only";
workerRequired: false;
}
@@ -97,7 +106,7 @@ export const LABORATORY_RECORDED_EVIDENCE_VIEWER_PROFILE = Object.freeze({
clock: "source-sequence",
cameraTransport: "generation-bound-fmp4",
spatialTransport: "bounded-sealed-artifacts",
loadPolicy: "visible-evidence-only",
loadPolicy: "explicit-legacy-comparison-only",
workerRequired: false,
} satisfies LaboratoryRecordedEvidenceViewerProfile);
@@ -27,6 +27,41 @@
display: block;
}
.m4-replay-threat-visual__unified-content {
position: absolute;
z-index: 1;
inset: 0;
min-width: 0;
min-height: 0;
}
.m4-replay-threat-visual__unified-content > *,
.m4-replay-threat-visual__unified-content .rerun-viewport {
width: 100%;
height: 100%;
}
.m4-replay-threat-visual__deck[data-native-rerun="true"] > .nodedc-split-pane {
position: relative;
z-index: 2;
pointer-events: none;
}
.m4-replay-threat-visual__deck[data-native-rerun="true"]
.m4-replay-threat-visual__pane {
background: transparent;
pointer-events: none;
}
.m4-replay-threat-visual__deck[data-native-rerun="true"]
.nodedc-split-pane__separator,
.m4-replay-threat-visual__deck[data-native-rerun="true"]
.m4-replay-threat-visual__pane-toolbar,
.m4-replay-threat-visual__deck[data-native-rerun="true"]
.m4-replay-threat-visual__pane-toolbar * {
pointer-events: auto;
}
.m4-replay-threat-visual__deck[data-empty="true"] > .l3-visual-audit__state {
position: absolute;
z-index: 1;
+1 -1
View File
@@ -78,7 +78,7 @@
overflow: hidden;
}
/* Rerun WebViewer 0.34.1 keeps three fixed 24px canvas rows even after its
/* The upstream Rerun canvas keeps three fixed 24px rows even after its
panels are overridden: the native top row, recording tab and view tab.
They are drawn inside WASM and cannot be styled independently, so crop the
fixed native chrome while keeping the actual 3D viewport full-height. */
@@ -0,0 +1,260 @@
import { useEffect, useMemo, useState } from "react";
import { Button, Icon, SegmentedControl } from "@nodedc/ui-react";
import { ObservationTimeline } from "../../components/ObservationTimeline";
import {
RerunViewport,
type RecordedPerceptionLoadState,
type RerunPlaybackController,
type RerunPlaybackState,
} from "../../components/RerunViewport";
import {
CanonicalRecordedLabReplay,
useCanonicalRecordedLabReplayState,
} from "../../components/laboratory/CanonicalRecordedLabReplay";
import type { VegetationFullRouteReview } from "../../core/laboratory/vegetationShadow";
import { recordedSessionRerunProfile } from "../../core/observation/viewerProfile";
import { resolveObservationSessionReplay } from "../../core/observation/useObservationSessions";
import { defaultSceneSettings } from "../../sceneSettings";
type MediaMode = "video" | "camera";
type SpatialMode = "3d" | "plan";
type SpatialLayer = "source" | "local" | "tgs" | "semantic";
type SemanticLayer = "city" | "vegetation";
const EMPTY_PERCEPTION_LOAD: RecordedPerceptionLoadState = {
phase: "idle",
receivedBytes: 0,
totalBytes: null,
progress: null,
message: "",
};
export function CanonicalVegetationRerunReplay({
resultId,
review,
}: {
resultId: string;
review: VegetationFullRouteReview;
}) {
const {
mediaMode,
spatialMode,
splitPrimarySize,
splitOrientation,
expanded,
onMediaModeChange,
onSpatialModeChange,
onSplitPrimarySizeChange,
onExpandedChange,
} = useCanonicalRecordedLabReplayState<MediaMode, SpatialMode>({
initialMediaMode: "video",
initialSpatialMode: "3d",
});
const [semanticLayer, setSemanticLayer] = useState<SemanticLayer>("vegetation");
const [showSemantics, setShowSemantics] = useState(true);
const [spatialLayer, setSpatialLayer] = useState<SpatialLayer>("source");
const [viewResetGeneration, setViewResetGeneration] = useState<0 | 1>(0);
const [playback, setPlayback] = useState<RerunPlaybackState | null>(null);
const [playbackController, setPlaybackController] =
useState<RerunPlaybackController | null>(null);
const [perceptionLoad, setPerceptionLoad] =
useState<RecordedPerceptionLoadState>(EMPTY_PERCEPTION_LOAD);
const [launch, setLaunch] = useState<Awaited<ReturnType<typeof resolveObservationSessionReplay>> | null>(null);
const [launchError, setLaunchError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLaunch(null);
setLaunchError(null);
void resolveObservationSessionReplay(review.sessionId, {
signal: controller.signal,
maximumWaitMs: 30 * 60 * 1000,
onUpdate: () => undefined,
}).then((value) => {
if (!controller.signal.aborted) setLaunch(value);
}).catch((caught: unknown) => {
if (!controller.signal.aborted) {
setLaunchError(
caught instanceof Error ? caught.message : "Каноническая запись RAV004 недоступна.",
);
}
});
return () => controller.abort();
}, [review.sessionId]);
const splitView = mediaMode !== null && spatialMode !== null;
const sceneSettings = useMemo(() => ({
...defaultSceneSettings,
accumulationSeconds: spatialLayer === "local" ? 5 : 0,
showPoints: spatialMode !== null,
showTrajectory: spatialMode !== null,
showGrid: spatialMode !== null,
pointSize: 2.2,
}), [spatialLayer, spatialMode]);
const profile = launch ? recordedSessionRerunProfile({
sourceUrl: launch.sourceUrl,
artifact: {
sourceUrl: launch.sourceUrl,
viewerSourceUrl: launch.viewerSourceUrl,
byteLength: launch.byteLength,
sha256: launch.sha256,
},
autoplayWhenReady: false,
presentationGate: "ready",
expectedTimelineStartSeconds: launch.timelineStartSeconds,
expectedTimelineEndSeconds: launch.timelineEndSeconds,
initialPlaybackStartSeconds: review.timelineStartSeconds,
view: mediaMode !== null ? "perception" : "spatial",
viewResetGeneration,
followTrajectory: false,
perceptionSourceUrl:
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}` +
"/canonical-overlay.rrd",
semanticLayer,
unifiedPerception: splitView,
planView: spatialMode === "plan",
perceptionLayers: {
enabled: mediaMode !== null,
detections2d: mediaMode === "video",
segmentation: mediaMode === "video" && showSemantics,
cuboids3d: false,
},
perceptionRetryGeneration: 0,
lockPerceptionCameraInteraction: false,
}) : null;
const mediaLayerControls = (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
aria-label="Слои камеры и видео"
>
<Button
size="compact"
shape="pill"
variant={showSemantics ? "primary" : "secondary"}
aria-pressed={showSemantics}
onClick={() => setShowSemantics((visible) => !visible)}
>
SEMANTICS
</Button>
<SegmentedControl
value={semanticLayer}
items={[
{ value: "city", label: "ГОРОД · EoMT" },
{ value: "vegetation", label: "ПРИРОДА · DDRNet" },
]}
label="Источник семантики"
onChange={(value) => {
setSemanticLayer(value);
setShowSemantics(true);
}}
/>
</div>
);
const spatialLayerControls = (
<div
className="m4-replay-threat-visual__pane-layer-controls"
role="group"
aria-label="Пространственные слои RAV004"
>
<SegmentedControl
value={spatialLayer}
items={[
{ value: "source", label: "SOURCE POINTS" },
{ value: "local", label: "LOCAL SLAM" },
{ value: "tgs", label: "TGS COSTMAP", disabled: true },
{ value: "semantic", label: "SEMANTICS", disabled: true },
]}
label="Пространственные слои"
onChange={setSpatialLayer}
/>
</div>
);
const resetSpatialView = (
<Button
size="compact"
variant="ghost"
icon={<Icon name="refresh" size={14} />}
aria-label="Сбросить положение 3D камеры"
title="Сбросить положение 3D камеры"
onClick={() => setViewResetGeneration((value) => value === 0 ? 1 : 0)}
>
</Button>
);
const transport = playback && playbackController ? (
<ObservationTimeline
className="m4-replay-threat-visual__timeline"
active
sourceCount={3}
mode="recorded"
seekable
synchronization="shared-clock"
rangeNs={playback.rangeNs}
currentNs={playback.currentNs}
playing={playback.playing}
onSeek={playbackController.seek}
onPlayingChange={playbackController.setPlaying}
showJumpToEnd={false}
/>
) : undefined;
const overlayMessage = launchError
?? (perceptionLoad.phase === "loading" ? perceptionLoad.message : null)
?? (perceptionLoad.phase === "error" ? perceptionLoad.message : null);
return (
<CanonicalRecordedLabReplay
label="RAVNOVES004TREE · upstream Rerun recorded replay"
mediaMode={mediaMode ?? "none"}
mediaModes={[
{ value: "video", label: "VIDEO" },
{ value: "camera", label: "CAMERA" },
]}
spatialMode={spatialMode ?? "none"}
spatialModes={[
{ value: "3d", label: "3D" },
{ value: "plan", label: "PLAN" },
]}
expanded={expanded}
splitPrimarySize={splitPrimarySize}
splitOrientation={splitOrientation}
mediaAriaLabel={mediaMode === "camera" ? "Камера" : "Видео и семантика"}
spatialAriaLabel={spatialMode === "plan" ? "Вид сверху" : "Трёхмерная сцена"}
mediaLayerControls={mediaLayerControls}
spatialLayerControls={spatialLayerControls}
spatialLeadingControl={resetSpatialView}
mediaMultiLayer
unifiedContent={profile ? (
<RerunViewport
profile={profile}
sceneSettings={sceneSettings}
onPlaybackChange={setPlayback}
onPlaybackControllerChange={setPlaybackController}
onPerceptionLoadChange={setPerceptionLoad}
/>
) : (
<div className="l3-visual-audit__state" role={launchError ? "alert" : "status"}>
{launchError ?? "Открываем каноническую запись RAV004…"}
</div>
)}
emptyMessage="Выберите VIDEO/CAMERA или 3D/PLAN. Общий Rerun-clock останется на месте."
deckOverlays={overlayMessage ? (
<div className="m4-replay-threat-visual__buffering" role="status">
{perceptionLoad.phase === "loading" ? (
<span className="busy-indicator" aria-hidden="true" />
) : (
<Icon name="alert" size={14} />
)}
<span>{overlayMessage}</span>
</div>
) : undefined}
transport={transport}
onMediaModeChange={onMediaModeChange}
onSpatialModeChange={onSpatialModeChange}
onExpandedChange={onExpandedChange}
onSplitPrimarySizeChange={onSplitPrimarySizeChange}
/>
);
}
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import {
LaboratoryEvidence,
@@ -7,7 +7,6 @@ import {
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import {
vegetationFullRouteMaskUrl,
vegetationVideoMaskUrl,
type VegetationFullRouteReview,
type VegetationShadowResult,
@@ -17,13 +16,7 @@ import {
type M49TgsFullShadowResult,
} from "../../core/laboratory/m49TgsFullShadow";
import { M49TgsFullShadowEvidence } from "./M49TgsFullShadowEvidence";
import {
M4ReplayThreatVisual,
type M4ReplayClassifiedSpatialLayer,
type M4ReplayThreatSemanticLayer,
} from "./M4ReplayThreatVisual";
const VEGETATION_TIMELINE_ENDPOINT = "/api/v1/laboratory/vegetation-shadow";
import { CanonicalVegetationRerunReplay } from "./CanonicalVegetationRerunReplay";
function decimal(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
@@ -36,56 +29,7 @@ function FullRouteReviewEvidence({
resultId: string;
review: VegetationFullRouteReview;
}) {
const semanticLayers = useMemo<readonly M4ReplayThreatSemanticLayer[]>(() => ([
{
id: "city",
controlLabel: "ГОРОД · EoMT",
resultId,
spatialResultId: null,
taxonomy: review.city.taxonomy,
maskUrl: (sequence) => vegetationFullRouteMaskUrl(resultId, "city", sequence),
label: review.city.name,
maskAriaLabel: "EoMT city semantic prediction",
},
{
id: "vegetation",
controlLabel: "ПРИРОДА · DDRNet",
resultId,
spatialResultId: null,
taxonomy: review.vegetation.taxonomy,
maskUrl: (sequence) => vegetationFullRouteMaskUrl(resultId, "vegetation", sequence),
label: review.vegetation.name,
maskAriaLabel: "DDRNet nature semantic prediction",
},
]), [resultId, review.city, review.vegetation]);
const sealedSpatialGap = useMemo<M4ReplayClassifiedSpatialLayer>(() => ({
label: "RAVNOVES004TREE",
pointLayerLabel: "SOURCE POINTS",
cellLayerLabel: "TGS COSTMAP",
cellLayerAvailable: false,
expectedAtSequence: false,
frame: null,
loading: false,
error: null,
replacePointCloud: false,
}), []);
return (
<M4ReplayThreatVisual
resultId={resultId}
timelineEndpointRoot={VEGETATION_TIMELINE_ENDPOINT}
semanticLayers={semanticLayers}
initialSemanticLayerId="vegetation"
initialSpatialMode="3d"
classifiedSpatialLayer={sealedSpatialGap}
evidenceLabel="RAVNOVES004TREE"
playbackTransport="segmented"
spatialPlaybackTransport="sealed-binary"
recoverTimestampStalls
showReferenceMediaLayers
showSpatialOverlaySummary
/>
);
return <CanonicalVegetationRerunReplay resultId={resultId} review={review} />;
}
function FullRouteReviewResult({
@@ -485,6 +485,8 @@ test("recorded blueprint fetch is bounded, strict and sends only display setting
view_reset_generation: 1,
follow_trajectory: true,
unified_perception: true,
semantic_layer: null,
plan_view: false,
show_detections_2d: true,
show_segmentation: false,
show_cuboids_3d: true,
@@ -13,7 +13,6 @@ let liveTimelineNeedsSynchronization;
let liveRerunReceiverBindingIdentity;
let recordedOpenWatchdogTimeoutMs;
let rerunViewerInitialSource;
let rerunViewerOpenOptions;
let resolveRecordedViewerSourceUrl;
before(async () => {
@@ -31,7 +30,6 @@ before(async () => {
liveRerunReceiverBindingIdentity,
recordedOpenWatchdogTimeoutMs,
rerunViewerInitialSource,
rerunViewerOpenOptions,
resolveRecordedViewerSourceUrl,
} = await server.ssrLoadModule("/src/components/RerunViewport.tsx"));
});
@@ -71,11 +69,6 @@ test("only the canonical digest-bound generation URL reaches the native Rerun re
}
});
test("only the live receiver opens on the native following edge", () => {
assert.deepEqual(rerunViewerOpenOptions(true), { follow_if_http: true });
assert.equal(rerunViewerOpenOptions(false), null);
});
test("live presentation waits for the exact receiver to expose a usable range", () => {
assert.equal(isLiveRerunPresentationReady(false, { min: 1, max: 2 }, 1), false);
assert.equal(isLiveRerunPresentationReady(true, null, 1), false);
@@ -216,7 +209,7 @@ test("recorded RRD bytes are never split across LogChannel.send_rrd calls", asyn
assert.doesNotMatch(source, /missioncore\/recorded-recording/);
assert.doesNotMatch(source, /recordedChannel/);
assert.match(source, /viewer\.start\(\s*rerunViewerInitialSource\(resolvedSource\)/s);
assert.match(source, /rerunViewerOpenOptions\(followLive\)/);
assert.doesNotMatch(source, /rerunViewerOpenOptions/);
assert.match(
source,
/recordingOpened = true;[\s\S]*diagnosticLifecycle\.markAdmitted\(\);/,
@@ -1,81 +1,36 @@
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";
import makeRerunRuntime from "../vendor/rerun-web-viewer-0.34.1/re_viewer.nodedc.js";
const root = resolve(import.meta.dirname, "..");
const packageRoot = resolve(root, "node_modules/@rerun-io/web-viewer");
const vendorRoot = resolve(root, "vendor/rerun-web-viewer-0.34.1");
const readJson = (path) => JSON.parse(readFileSync(path, "utf8"));
const sha256 = (path) =>
createHash("sha256").update(readFileSync(path)).digest("hex");
test("Mission Core uses the exact upstream Rerun 0.36.3 web package", () => {
const application = readJson(resolve(root, "package.json"));
const installed = readJson(resolve(packageRoot, "package.json"));
test("NODE.DC Rerun runtime is the audited 0.34.1 spatial camera build", () => {
const manifest = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8"));
assert.equal(manifest.version, "0.34.1");
const expectedWasm = "ffe7543d28bb3394f289f6299de43d038767eef83d781c2b7f8f5683308a0469";
const expectedGlue = "0f7b76c9f24cbd8437021b5d37499894aeadc586183e422ebc82ef556d7b8339";
assert.equal(sha256(resolve(vendorRoot, "re_viewer_bg.nodedc.wasm")), expectedWasm);
assert.equal(sha256(resolve(vendorRoot, "re_viewer.nodedc.js")), expectedGlue);
assert.equal(sha256(resolve(packageRoot, "re_viewer_bg.wasm")), expectedWasm);
assert.equal(sha256(resolve(packageRoot, "re_viewer.js")), expectedGlue);
assert.equal(application.dependencies["@rerun-io/web-viewer"], "0.36.3");
assert.equal(installed.version, "0.36.3");
assert.equal(application.scripts.postinstall, undefined);
});
test("custom JavaScript glue references only exports present in its paired WASM", () => {
const wasmPath = resolve(vendorRoot, "re_viewer_bg.nodedc.wasm");
const gluePath = resolve(vendorRoot, "re_viewer.nodedc.js");
const module = new WebAssembly.Module(readFileSync(wasmPath));
const exports = new Set(WebAssembly.Module.exports(module).map(({ name }) => name));
const imports = WebAssembly.Module.imports(module);
const glue = readFileSync(gluePath, "utf8");
const referencedExports = new Set(
[...glue.matchAll(/\bwasm\.([A-Za-z_$][\w$]*)/g)].map((match) => match[1]),
);
const missingExports = [...referencedExports].filter((name) => !exports.has(name));
test("the active application never imports or installs the archived vendor fork", () => {
const application = readFileSync(resolve(root, "package.json"), "utf8");
const viewport = readFileSync(resolve(root, "src/components/RerunViewport.tsx"), "utf8");
assert.equal(imports.length, 927);
assert.equal(exports.size, 79);
assert.deepEqual(missingExports, []);
assert.match(glue, /export default function\(\)/);
assert.match(glue, /if \(!wasm\) return;/);
assert.doesNotMatch(application, /patch-rerun-web-viewer/);
assert.doesNotMatch(application, /vendor\/rerun-web-viewer/);
assert.doesNotMatch(viewport, /vendor\/rerun-web-viewer/);
assert.doesNotMatch(viewport, /0\.34\.1/);
});
test("custom Rerun WASM initializes and grows its externref table", () => {
const runtime = makeRerunRuntime();
runtime.initSync({
module: readFileSync(resolve(vendorRoot, "re_viewer_bg.nodedc.wasm")),
});
test("upstream WebViewer exposes one three-argument start contract", () => {
const declaration = readFileSync(resolve(packageRoot, "index.d.ts"), "utf8");
assert.equal(typeof runtime.WebHandle, "function");
runtime.deinit();
});
test("source patch carries pointer navigation, persistent follow, and camera continuity tests", () => {
const patch = readFileSync(resolve(vendorRoot, "NODEDC_ZOOM_TO_CURSOR.patch"), "utf8");
assert.match(patch, /fn pointer_ray_direction/);
assert.match(patch, /fn zoom_orbit_towards_pointer/);
assert.match(patch, /near_limit_hands_excess_zoom_to_cursor_directed_dolly/);
assert.match(patch, /crossing_near_limit_preserves_unconsumed_scene_scaled_zoom/);
assert.match(patch, /remaining_zoom_factor\.ln\(\) \* self\.speed/);
assert.match(patch, /off_center_pointer_stays_on_the_same_view_ray/);
assert.match(patch, /fn rotate_radians_around_anchor/);
assert.match(patch, /orbit_drag_anchor/);
assert.match(patch, /minimum_orbital_navigation_speed/);
assert.match(patch, /orbital_rotation_keeps_selected_anchor_on_the_same_view_ray/);
assert.match(patch, /orbital_navigation_speed_floor_tracks_scene_scale/);
assert.match(patch, /NODEDC_PERSISTENT_ORBIT_TRACKING_ENTITY/);
assert.match(patch, /nodedc_rig_orbit_tracking_is_persistent/);
assert.match(patch, /restore_persistent_orbit_eye_after_blueprint_update/);
assert.match(
patch,
/persistent_rig_follow_restores_the_last_rendered_eye_after_blueprint_update/,
declaration,
/start\(rrd: string \| string\[\] \| null, parent: HTMLElement \| null, options: WebViewerOptions \| null\): Promise<void>/,
);
assert.match(patch, /explicit_blueprint_pose_is_not_replaced_by_the_previous_eye/);
assert.match(patch, /previous_picking_result/);
});
@@ -451,8 +451,8 @@ test("canonical recorded LAB spatial frame keeps source, SLAM and body identity
assert.deepEqual(frame.bodyFrame.originMapXyzM, [33, 4, 1]);
});
test("vegetation realtime LAB and archival benchmark use separate admitted instruments", async () => {
const [resultSource, benchmarkSource, m49Source, canonicalSource] = await Promise.all([
test("vegetation realtime LAB uses one upstream Rerun clock and keeps archival review separate", async () => {
const [resultSource, benchmarkSource, m49Source, canonicalSource, rerunSource] = await Promise.all([
readFile(
new URL("../src/workspaces/laboratory/VegetationShadowResult.tsx", import.meta.url),
"utf8",
@@ -469,6 +469,10 @@ test("vegetation realtime LAB and archival benchmark use separate admitted instr
new URL("../src/components/laboratory/CanonicalRecordedLabReplay.tsx", import.meta.url),
"utf8",
),
readFile(
new URL("../src/workspaces/laboratory/CanonicalVegetationRerunReplay.tsx", import.meta.url),
"utf8",
),
]);
assert.doesNotMatch(resultSource, /M48MaskComparisonVisual/);
assert.match(resultSource, /M49TgsFullShadowEvidence/);
@@ -478,18 +482,20 @@ test("vegetation realtime LAB and archival benchmark use separate admitted instr
assert.equal(resultSource.match(/<LaboratoryEvidence\b/g)?.length, 2);
assert.doesNotMatch(resultSource, /RAVNOVES004TREE mixed route review/);
assert.match(resultSource, /CANONICAL RECORDED LAB · RAVNOVES004TREE/);
assert.match(resultSource, /<M4ReplayThreatVisual/);
assert.match(resultSource, /timelineEndpointRoot=\{VEGETATION_TIMELINE_ENDPOINT\}/);
assert.match(resultSource, /playbackTransport="segmented"/);
assert.match(resultSource, /recoverTimestampStalls/);
assert.match(resultSource, /<CanonicalVegetationRerunReplay/);
assert.doesNotMatch(resultSource, /RerunViewport/);
assert.doesNotMatch(resultSource, /cacheRef|pumpRef|desiredRef/);
assert.doesNotMatch(resultSource, /LaboratoryRecordedClipPlayer|M48EvidenceModeRail/);
assert.doesNotMatch(resultSource, /assets\.tgs|<img/);
assert.match(resultSource, /SOURCE POINTS/);
assert.match(resultSource, /TGS COSTMAP/);
assert.match(rerunSource, /<RerunViewport/);
assert.match(rerunSource, /SOURCE POINTS/);
assert.match(rerunSource, /LOCAL SLAM/);
assert.match(rerunSource, /TGS COSTMAP/);
assert.match(rerunSource, /canonical-overlay\.rrd/);
assert.match(rerunSource, /unifiedPerception: splitView/);
assert.doesNotMatch(rerunSource, /LaboratoryRecordedClipPlayer|LaboratoryMetricEvidenceScene/);
assert.match(resultSource, /point-aligned 3D semantics пока не запечатаны/);
assert.match(resultSource, /cellLayerAvailable: false/);
assert.match(rerunSource, /value: "tgs", label: "TGS COSTMAP", disabled: true/);
assert.match(canonicalSource, /primary=\{mediaPane\}/);
assert.match(canonicalSource, /secondary=\{spatialPane/);
assert.match(canonicalSource, /missioncore\.canonical-recorded-lab-replay\/v1/);
@@ -75,13 +75,13 @@ test("recorded session profile owns progressive admission and on-demand layers",
assert.equal(profile.perceptionLayers.enabled, false);
});
test("LAB recorded evidence remains outside native Rerun lifecycle", () => {
test("the old LAB transport is fenced as explicit legacy comparison only", () => {
assert.deepEqual(LABORATORY_RECORDED_EVIDENCE_VIEWER_PROFILE, {
kind: "lab-recorded-evidence",
clock: "source-sequence",
cameraTransport: "generation-bound-fmp4",
spatialTransport: "bounded-sealed-artifacts",
loadPolicy: "visible-evidence-only",
loadPolicy: "explicit-legacy-comparison-only",
workerRequired: false,
});
assert.equal(Object.isFrozen(LABORATORY_RECORDED_EVIDENCE_VIEWER_PROFILE), true);
+10
View File
@@ -0,0 +1,10 @@
# Archived viewer comparison source
`rerun-web-viewer-0.34.1/` is retained only as rollback-era comparison evidence
for ADR 0045. The active application neither imports it nor runs the historical
patch scripts during install or build. Canonical Control Station routes use the
unmodified `@rerun-io/web-viewer` dependency declared in `package.json`.
Do not update or reactivate this tree. Remove it with the remaining custom
fMP4/Three.js replay implementation after the canonical Rerun migration passes
operator visual acceptance.