refactor(lab): объединить RAV004 в единый Rerun replay
This commit is contained in:
@@ -36,7 +36,6 @@ import {
|
||||
createRecordedAutoplayGate,
|
||||
createRecordedInitialSeekGate,
|
||||
createRecordedOpenWatchdog,
|
||||
shouldReapplyRecordedBlueprint,
|
||||
} from "../core/observation/recordedRerunLifecycle";
|
||||
import type {
|
||||
RecordedPerceptionLayers,
|
||||
@@ -72,7 +71,6 @@ export {
|
||||
recordedPlaybackRangeWhenReady,
|
||||
recordedPointColorKey,
|
||||
rerunPresentationStatus,
|
||||
shouldReapplyRecordedBlueprint,
|
||||
type RecordedPlaybackBufferState,
|
||||
} from "../core/observation/recordedRerunLifecycle";
|
||||
export {
|
||||
@@ -167,6 +165,7 @@ interface RecordedRerunIdentity {
|
||||
}
|
||||
|
||||
const RECORDED_RRD_PATH = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/recording\.rrd$/;
|
||||
const LAB_RECORDED_REPLAY_PATH = /^\/api\/v1\/laboratory\/vegetation-shadow\/lab-v1-vegetation-shadow-[a-f0-9]{64}\/canonical-replay\.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$/;
|
||||
@@ -231,7 +230,8 @@ export function resolveRecordedViewerSourceUrl(
|
||||
const expectedViewerSourceUrl = `${descriptor.sourceUrl}?generation=${descriptor.sha256}`;
|
||||
const endpoint = new URL(descriptor.viewerSourceUrl, `${base.origin}/`);
|
||||
if (
|
||||
!RECORDED_RRD_PATH.test(descriptor.sourceUrl) ||
|
||||
!(RECORDED_RRD_PATH.test(descriptor.sourceUrl)
|
||||
|| LAB_RECORDED_REPLAY_PATH.test(descriptor.sourceUrl)) ||
|
||||
descriptor.viewerSourceUrl !== expectedViewerSourceUrl ||
|
||||
endpoint.origin !== base.origin ||
|
||||
endpoint.pathname !== descriptor.sourceUrl ||
|
||||
@@ -252,10 +252,23 @@ export function rerunViewerInitialSource(
|
||||
return resolvedSourceUrl;
|
||||
}
|
||||
|
||||
export function resolveRecordedBlueprintUrl(sourceUrl: string, origin: string): string | null {
|
||||
export function resolveRecordedBlueprintUrl(
|
||||
sourceUrl: string,
|
||||
origin: string,
|
||||
explicitSourceUrl?: string,
|
||||
): string | null {
|
||||
const normalized = sourceUrl.trim();
|
||||
if (!RECORDED_RRD_PATH.test(normalized)) return null;
|
||||
const base = new URL(origin);
|
||||
if (explicitSourceUrl !== undefined) {
|
||||
const explicit = explicitSourceUrl.trim();
|
||||
if (
|
||||
!LAB_RECORDED_REPLAY_PATH.test(normalized)
|
||||
|| !RECORDED_BLUEPRINT_PATH.test(explicit)
|
||||
) return null;
|
||||
const endpoint = new URL(explicit, `${base.origin}/`);
|
||||
return endpoint.origin === base.origin ? endpoint.href : null;
|
||||
}
|
||||
if (!RECORDED_RRD_PATH.test(normalized)) return null;
|
||||
const endpoint = new URL(
|
||||
normalized.replace(/\/recording\.rrd$/, "/blueprint.rrd"),
|
||||
`${base.origin}/`,
|
||||
@@ -690,6 +703,7 @@ export function RerunViewport({
|
||||
segmentation: false,
|
||||
cuboids3d: false,
|
||||
};
|
||||
const recordedBlueprintSourceUrl = recordedProfile?.blueprintSourceUrl;
|
||||
const recordedPerceptionSourceUrl = recordedProfile?.perceptionSourceUrl;
|
||||
const recordedSemanticLayer = recordedProfile?.semanticLayer;
|
||||
const recordedUnifiedPerception = recordedProfile?.unifiedPerception ?? false;
|
||||
@@ -727,7 +741,11 @@ export function RerunViewport({
|
||||
const [blueprintChannelRevision, setBlueprintChannelRevision] = useState(0);
|
||||
const [perceptionChannelRevision, setPerceptionChannelRevision] = useState(0);
|
||||
const recordedBlueprintUrl = sourceUrl
|
||||
? resolveRecordedBlueprintUrl(sourceUrl, window.location.origin)
|
||||
? resolveRecordedBlueprintUrl(
|
||||
sourceUrl,
|
||||
window.location.origin,
|
||||
recordedBlueprintSourceUrl,
|
||||
)
|
||||
: null;
|
||||
const recordedPerceptionUrl = recordedPerceptionSourceUrl
|
||||
? resolveRerunSourceUrl(recordedPerceptionSourceUrl, window.location.origin)
|
||||
@@ -773,7 +791,8 @@ export function RerunViewport({
|
||||
return;
|
||||
}
|
||||
|
||||
const isRecordedSource = RECORDED_RRD_PATH.test(normalizedSource);
|
||||
const isRecordedSource = RECORDED_RRD_PATH.test(normalizedSource)
|
||||
|| LAB_RECORDED_REPLAY_PATH.test(normalizedSource);
|
||||
let resolvedSource: string;
|
||||
try {
|
||||
if (isRecordedSource) {
|
||||
@@ -1151,23 +1170,7 @@ export function RerunViewport({
|
||||
disposed ||
|
||||
(isRecordedSource && event.application_id !== "nodedc_mission_core_recorded")
|
||||
) return;
|
||||
if (recordingOpened) {
|
||||
const identity = recordedIdentityRef.current;
|
||||
if (shouldReapplyRecordedBlueprint(
|
||||
recordingOpened,
|
||||
isRecordedSource,
|
||||
recordedBlueprintUrl !== null,
|
||||
identity,
|
||||
event,
|
||||
)) {
|
||||
// A LAB sidecar is a second upstream receiver for the same
|
||||
// recording store. Rerun re-opens that store only after the
|
||||
// sidecar has been decoded, so reapply the canonical blueprint
|
||||
// at this exact boundary instead of racing it during download.
|
||||
setBlueprintChannelRevision((revision) => revision + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (recordingOpened) return;
|
||||
recordingOpened = true;
|
||||
if (!isRecordedSource) {
|
||||
// Store discovery only establishes a candidate. Admission is
|
||||
|
||||
@@ -242,7 +242,7 @@ export function CanonicalRecordedLabReplay<
|
||||
minPrimarySize={splitView ? 24 : 0}
|
||||
minSecondarySize={splitView ? 24 : 0}
|
||||
resizable={splitView}
|
||||
separatorLabel="Изменить размер VIDEO/CAMERA и 3D/PLAN"
|
||||
separatorLabel="Изменить размер видео/камеры и 3D/плана"
|
||||
/>
|
||||
{mediaMode === "none" && spatialMode === "none" ? (
|
||||
<div className="l3-visual-audit__state" role="status">
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { ObservationSessionReplayLaunch } from "../observation/sessionArchive";
|
||||
import type { RecordedRrdArtifactDescriptor } from "../observation/viewerProfile";
|
||||
|
||||
const SAFE_RESULT_ID = /^lab-v1-vegetation-shadow-[a-f0-9]{64}$/;
|
||||
const SAFE_SESSION_SOURCE = /^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/recording\.rrd$/;
|
||||
const MAX_CANONICAL_REPLAY_BYTES = 1024 * 1024 * 1024;
|
||||
|
||||
export interface CanonicalLabReplayDescriptor extends RecordedRrdArtifactDescriptor {
|
||||
blueprintSourceUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the one immutable RRD used by the canonical recorded LAB.
|
||||
*
|
||||
* The server caches the merge of the sealed spatial recording and the LAB AI
|
||||
* evidence. Rerun therefore opens one source and cannot present the base store
|
||||
* before a second receiver has finished decoding the semantic layer.
|
||||
*/
|
||||
export async function resolveCanonicalLabReplay(
|
||||
resultId: string,
|
||||
launch: ObservationSessionReplayLaunch,
|
||||
{
|
||||
origin = window.location.origin,
|
||||
signal,
|
||||
fetcher = globalThis.fetch,
|
||||
}: {
|
||||
origin?: string;
|
||||
signal?: AbortSignal;
|
||||
fetcher?: typeof globalThis.fetch;
|
||||
} = {},
|
||||
): Promise<CanonicalLabReplayDescriptor> {
|
||||
const base = new URL(origin);
|
||||
if (
|
||||
!SAFE_RESULT_ID.test(resultId)
|
||||
|| !SAFE_SESSION_SOURCE.test(launch.sourceUrl)
|
||||
|| launch.viewerSourceUrl !== `${launch.sourceUrl}?generation=${launch.sha256}`
|
||||
|| !/^[a-f0-9]{64}$/.test(launch.sha256)
|
||||
) {
|
||||
throw new Error("Канонический replay LAB имеет небезопасный descriptor.");
|
||||
}
|
||||
const sourceUrl =
|
||||
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}`
|
||||
+ "/canonical-replay.rrd";
|
||||
const descriptorUrl = new URL(sourceUrl, `${base.origin}/`);
|
||||
descriptorUrl.searchParams.set("base_generation", launch.sha256);
|
||||
if (descriptorUrl.origin !== base.origin) {
|
||||
throw new Error("Канонический replay LAB должен быть same-origin.");
|
||||
}
|
||||
const response = await fetcher(descriptorUrl.href, {
|
||||
method: "HEAD",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/vnd.rerun.rrd" },
|
||||
signal,
|
||||
});
|
||||
const contentType = response.headers.get("Content-Type")?.split(";", 1)[0].trim();
|
||||
const byteLength = Number(response.headers.get("Content-Length"));
|
||||
const sha256 = response.headers.get("ETag")?.match(/^"([a-f0-9]{64})"$/)?.[1];
|
||||
if (
|
||||
response.status !== 200
|
||||
|| contentType !== "application/vnd.rerun.rrd"
|
||||
|| response.headers.get("X-Rerun-Format") !== "RRF2"
|
||||
|| !sha256
|
||||
|| !Number.isSafeInteger(byteLength)
|
||||
|| byteLength < 4
|
||||
|| byteLength > MAX_CANONICAL_REPLAY_BYTES
|
||||
) {
|
||||
throw new Error("Единый replay LAB не прошёл проверку.");
|
||||
}
|
||||
return {
|
||||
sourceUrl,
|
||||
viewerSourceUrl: `${sourceUrl}?generation=${sha256}`,
|
||||
byteLength,
|
||||
sha256,
|
||||
blueprintSourceUrl: launch.sourceUrl.replace(/\/recording\.rrd$/, "/blueprint.rrd"),
|
||||
};
|
||||
}
|
||||
@@ -196,20 +196,6 @@ export function isRecordedPlaybackFullyBuffered(
|
||||
return recordedPlaybackBufferState(rangeNs, expectedTimelineEndSeconds).fullyBuffered;
|
||||
}
|
||||
|
||||
export function shouldReapplyRecordedBlueprint(
|
||||
recordingOpened: boolean,
|
||||
recordedSource: boolean,
|
||||
hasBlueprint: boolean,
|
||||
identity: { applicationId: string; recordingId: string } | null,
|
||||
event: { application_id: string; recording_id: string },
|
||||
): boolean {
|
||||
return recordingOpened
|
||||
&& recordedSource
|
||||
&& hasBlueprint
|
||||
&& identity?.applicationId === event.application_id
|
||||
&& identity.recordingId === event.recording_id;
|
||||
}
|
||||
|
||||
export function attemptRecordedAutoplay(
|
||||
seekToStart: () => void,
|
||||
startPlaying: () => void,
|
||||
|
||||
@@ -67,6 +67,8 @@ export interface RecordedSessionRerunProfile {
|
||||
viewResetGeneration: 0 | 1;
|
||||
followTrajectory: boolean;
|
||||
perceptionLayers: RecordedPerceptionLayers;
|
||||
/** Explicit small blueprint endpoint when the viewer source is a merged LAB RRD. */
|
||||
blueprintSourceUrl?: string;
|
||||
/** Optional immutable RRD sidecar for LAB/model evidence on the same recording clock. */
|
||||
perceptionSourceUrl?: string;
|
||||
/** Selects one semantic entity without changing the sealed sidecar. */
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Button, Icon, SegmentedControl } from "@nodedc/ui-react";
|
||||
import { ObservationTimeline } from "../../components/ObservationTimeline";
|
||||
import {
|
||||
RerunViewport,
|
||||
type RecordedPerceptionLoadState,
|
||||
type RerunPlaybackController,
|
||||
type RerunPlaybackState,
|
||||
} from "../../components/RerunViewport";
|
||||
@@ -12,7 +11,12 @@ import {
|
||||
CanonicalRecordedLabReplay,
|
||||
useCanonicalRecordedLabReplayState,
|
||||
} from "../../components/laboratory/CanonicalRecordedLabReplay";
|
||||
import {
|
||||
resolveCanonicalLabReplay,
|
||||
type CanonicalLabReplayDescriptor,
|
||||
} from "../../core/laboratory/canonicalLabReplay";
|
||||
import type { VegetationFullRouteReview } from "../../core/laboratory/vegetationShadow";
|
||||
import type { ObservationSessionReplayLaunch } from "../../core/observation/sessionArchive";
|
||||
import { recordedSessionRerunProfile } from "../../core/observation/viewerProfile";
|
||||
import { resolveObservationSessionReplay } from "../../core/observation/useObservationSessions";
|
||||
import { defaultSceneSettings } from "../../sceneSettings";
|
||||
@@ -22,13 +26,10 @@ 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: "",
|
||||
};
|
||||
interface CanonicalReplayLaunch {
|
||||
base: ObservationSessionReplayLaunch;
|
||||
replay: CanonicalLabReplayDescriptor;
|
||||
}
|
||||
|
||||
export function CanonicalVegetationRerunReplay({
|
||||
resultId,
|
||||
@@ -58,9 +59,7 @@ export function CanonicalVegetationRerunReplay({
|
||||
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 [launch, setLaunch] = useState<CanonicalReplayLaunch | null>(null);
|
||||
const [launchError, setLaunchError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,7 +70,12 @@ export function CanonicalVegetationRerunReplay({
|
||||
signal: controller.signal,
|
||||
maximumWaitMs: 30 * 60 * 1000,
|
||||
onUpdate: () => undefined,
|
||||
}).then((value) => {
|
||||
}).then(async (value) => ({
|
||||
base: value,
|
||||
replay: await resolveCanonicalLabReplay(resultId, value, {
|
||||
signal: controller.signal,
|
||||
}),
|
||||
})).then((value) => {
|
||||
if (!controller.signal.aborted) setLaunch(value);
|
||||
}).catch((caught: unknown) => {
|
||||
if (!controller.signal.aborted) {
|
||||
@@ -81,7 +85,7 @@ export function CanonicalVegetationRerunReplay({
|
||||
}
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [review.sessionId]);
|
||||
}, [resultId, review.sessionId]);
|
||||
|
||||
const splitView = mediaMode !== null && spatialMode !== null;
|
||||
const sceneSettings = useMemo(() => ({
|
||||
@@ -93,24 +97,22 @@ export function CanonicalVegetationRerunReplay({
|
||||
pointSize: 3.8,
|
||||
}), [spatialLayer, spatialMode]);
|
||||
const profile = launch ? recordedSessionRerunProfile({
|
||||
sourceUrl: launch.sourceUrl,
|
||||
sourceUrl: launch.replay.sourceUrl,
|
||||
artifact: {
|
||||
sourceUrl: launch.sourceUrl,
|
||||
viewerSourceUrl: launch.viewerSourceUrl,
|
||||
byteLength: launch.byteLength,
|
||||
sha256: launch.sha256,
|
||||
sourceUrl: launch.replay.sourceUrl,
|
||||
viewerSourceUrl: launch.replay.viewerSourceUrl,
|
||||
byteLength: launch.replay.byteLength,
|
||||
sha256: launch.replay.sha256,
|
||||
},
|
||||
blueprintSourceUrl: launch.replay.blueprintSourceUrl,
|
||||
autoplayWhenReady: false,
|
||||
presentationGate: "ready",
|
||||
expectedTimelineStartSeconds: launch.timelineStartSeconds,
|
||||
expectedTimelineEndSeconds: launch.timelineEndSeconds,
|
||||
expectedTimelineStartSeconds: launch.base.timelineStartSeconds,
|
||||
expectedTimelineEndSeconds: launch.base.timelineEndSeconds,
|
||||
initialPlaybackStartSeconds: review.timelineStartSeconds,
|
||||
view: mediaMode !== null ? "perception" : "spatial",
|
||||
viewResetGeneration,
|
||||
followTrajectory: true,
|
||||
perceptionSourceUrl:
|
||||
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}` +
|
||||
"/canonical-overlay.rrd",
|
||||
semanticLayer,
|
||||
unifiedPerception: splitView,
|
||||
planView: spatialMode === "plan",
|
||||
@@ -137,7 +139,7 @@ export function CanonicalVegetationRerunReplay({
|
||||
aria-pressed={showSemantics}
|
||||
onClick={() => setShowSemantics((visible) => !visible)}
|
||||
>
|
||||
SEMANTICS
|
||||
СЕМАНТИКА
|
||||
</Button>
|
||||
<SegmentedControl
|
||||
value={semanticLayer}
|
||||
@@ -163,10 +165,10 @@ export function CanonicalVegetationRerunReplay({
|
||||
<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 },
|
||||
{ value: "source", label: "ИСХ. ТОЧКИ" },
|
||||
{ value: "local", label: "ЛОК. SLAM" },
|
||||
{ value: "tgs", label: "TGS", disabled: true },
|
||||
{ value: "semantic", label: "СЕМАНТИКА", disabled: true },
|
||||
]}
|
||||
label="Пространственные слои"
|
||||
size="dense"
|
||||
@@ -202,22 +204,18 @@ export function CanonicalVegetationRerunReplay({
|
||||
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"
|
||||
label="RAVNOVES004TREE · канонический повтор Rerun"
|
||||
mediaMode={mediaMode ?? "none"}
|
||||
mediaModes={[
|
||||
{ value: "video", label: "VIDEO" },
|
||||
{ value: "camera", label: "CAMERA" },
|
||||
{ value: "video", label: "ВИДЕО" },
|
||||
{ value: "camera", label: "КАМЕРА" },
|
||||
]}
|
||||
spatialMode={spatialMode ?? "none"}
|
||||
spatialModes={[
|
||||
{ value: "3d", label: "3D" },
|
||||
{ value: "plan", label: "PLAN" },
|
||||
{ value: "plan", label: "ПЛАН" },
|
||||
]}
|
||||
expanded={expanded}
|
||||
splitPrimarySize={splitPrimarySize}
|
||||
@@ -234,24 +232,13 @@ export function CanonicalVegetationRerunReplay({
|
||||
sceneSettings={sceneSettings}
|
||||
onPlaybackChange={setPlayback}
|
||||
onPlaybackControllerChange={setPlaybackController}
|
||||
onPerceptionLoadChange={setPerceptionLoad}
|
||||
/>
|
||||
) : (
|
||||
<div className="l3-visual-audit__state" role={launchError ? "alert" : "status"}>
|
||||
{launchError ?? "Открываем каноническую запись RAV004…"}
|
||||
{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}
|
||||
emptyMessage="Выберите ВИДЕО/КАМЕРА или 3D/ПЛАН. Общие часы Rerun останутся на месте."
|
||||
transport={transport}
|
||||
onMediaModeChange={onMediaModeChange}
|
||||
onSpatialModeChange={onSpatialModeChange}
|
||||
|
||||
@@ -46,16 +46,16 @@ function FullRouteReviewResult({
|
||||
summary={(
|
||||
<LaboratorySummary
|
||||
title="LAB V1 · RAVNOVES004TREE · полный маршрут"
|
||||
description="Принятый recorded-LAB инструмент воспроизводит RAV004 без отдельного viewer: одна media-clock timeline, RIGHT camera, source points, bounded Local SLAM и переключаемые EoMT/DDRNet."
|
||||
status="FULL RECORDED REVIEW · truth отсутствует · commands OFF"
|
||||
description="Принятый инструмент записанной LAB воспроизводит RAV004 без отдельного viewer: единый таймлайн, правая камера, исходные точки, ограниченный Local SLAM и переключаемые EoMT/DDRNet."
|
||||
status="ПОЛНЫЙ ПРОСМОТР ЗАПИСИ · эталон отсутствует · команды ВЫКЛ"
|
||||
statusTone="warning"
|
||||
facts={[
|
||||
{ label: "Источник", value: `${review.sourceId} · ${review.frameCount}/${review.frameCount} camera frames` },
|
||||
{ label: "3D", value: "1444 source cloud increments · gravity-stable RFU → body" },
|
||||
{ label: "Город", value: `${review.city.name} · ${decimal(review.city.inferenceFps, 2)} fps` },
|
||||
{ label: "Природа", value: `${review.vegetation.name} · ${decimal(review.vegetation.inferenceFps, 2)} fps` },
|
||||
{ label: "TGS", value: "10 review anchors существуют · full-route artifact отсутствует" },
|
||||
{ label: "Authority", value: `${rigLabel} · VISUAL REVIEW ONLY · commands OFF` },
|
||||
{ label: "Источник", value: `${review.sourceId} · ${review.frameCount}/${review.frameCount} кадров камеры` },
|
||||
{ label: "3D", value: "1444 приращения исходного облака · стабильная по гравитации RFU → корпус" },
|
||||
{ label: "Город", value: `${review.city.name} · ${decimal(review.city.inferenceFps, 2)} кадра/с` },
|
||||
{ label: "Природа", value: `${review.vegetation.name} · ${decimal(review.vegetation.inferenceFps, 2)} кадра/с` },
|
||||
{ label: "TGS", value: "существуют 10 контрольных якорей · артефакт полного маршрута отсутствует" },
|
||||
{ label: "Полномочия", value: `${rigLabel} · ТОЛЬКО ВИЗУАЛЬНЫЙ ПРОСМОТР · команды ВЫКЛ` },
|
||||
]}
|
||||
brief={{
|
||||
question: "Что реально видно на полном RAV004-прогоне с высокой травой, оврагами и переходом к городу?",
|
||||
@@ -78,8 +78,8 @@ function FullRouteReviewResult({
|
||||
)}
|
||||
evidence={(
|
||||
<LaboratoryEvidence
|
||||
eyebrow="CANONICAL RECORDED LAB · RAVNOVES004TREE"
|
||||
title="CAMERA + SOURCE POINTS + LOCAL SLAM + TGS COSTMAP + SEMANTICS · 6830/6830"
|
||||
eyebrow="КАНОНИЧЕСКАЯ ЗАПИСАННАЯ LAB · RAVNOVES004TREE"
|
||||
title="КАМЕРА + ИСХОДНЫЕ ТОЧКИ + ЛОКАЛЬНЫЙ SLAM + КАРТА TGS + СЕМАНТИКА · 6830/6830"
|
||||
kind="recorded-replay"
|
||||
resizable
|
||||
>
|
||||
@@ -89,18 +89,18 @@ function FullRouteReviewResult({
|
||||
result={(
|
||||
<LaboratoryResultSummary
|
||||
title="RAV004 переведён на общий replay-каркас; safety evidence ещё не полно"
|
||||
status="Recorded evidence · navigation/actuation OFF"
|
||||
status="Записанные доказательства · навигация/управление ВЫКЛ"
|
||||
statusTone="warning"
|
||||
metrics={[
|
||||
{ label: "Camera timeline", value: "6830 frames · ≈9.51 Hz", hint: "media clock owns video, overlays and spatial" },
|
||||
{ label: "Source geometry", value: "1444 increments · ≈2 Hz", hint: "last proven spatial frame is held between source arrivals" },
|
||||
{ label: "EoMT throughput", value: `${decimal(review.city.inferenceFps, 2)} fps`, hint: "изолированный full pass; не realtime stack" },
|
||||
{ label: "DDRNet throughput", value: `${decimal(review.vegetation.inferenceFps, 2)} fps`, hint: "изолированный full pass; temporal stability не принята" },
|
||||
{ label: "Таймлайн камеры", value: "6830 кадров · ≈9,51 Гц", hint: "единые часы управляют видео, слоями и пространством" },
|
||||
{ label: "Исходная геометрия", value: "1444 приращения · ≈2 Гц", hint: "между поступлениями удерживается последний подтверждённый пространственный кадр" },
|
||||
{ label: "Пропускная способность EoMT", value: `${decimal(review.city.inferenceFps, 2)} кадра/с`, hint: "изолированный полный прогон; не стек реального времени" },
|
||||
{ label: "Пропускная способность DDRNet", value: `${decimal(review.vegetation.inferenceFps, 2)} кадра/с`, hint: "изолированный полный прогон; временная стабильность не принята" },
|
||||
]}
|
||||
conclusion={{
|
||||
proved: "Camera, seek, spatial layers and semantic switching use one accepted reusable viewer and one media clock; RFU source geometry no longer inherits LiDAR roll/pitch.",
|
||||
notProved: "Не доказаны continuous TGS, независимый detector/STOP, truth accuracy, temporal stability DDRNet и ≥10 FPS совместного live stack.",
|
||||
decision: "Продолжать как visual audit. До запечатанного full-route TGS и detector/load gate navigation/actuation остаются OFF.",
|
||||
proved: "Камера, перемотка, пространственные слои и переключение семантики используют один принятый переиспользуемый viewer и единые часы; исходная геометрия RFU больше не наследует крен и тангаж LiDAR.",
|
||||
notProved: "Не доказаны непрерывная TGS, независимый детектор/STOP, точность относительно эталона, временная стабильность DDRNet и ≥10 кадров/с совместного стека реального времени.",
|
||||
decision: "Продолжать как визуальный аудит. До запечатанной TGS полного маршрута и барьера детектора/нагрузки навигация и управление остаются выключенными.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -104,24 +104,3 @@ test("laboratory UI is a bounded feature slice, not a central workspace branch",
|
||||
assert.doesNotMatch(laboratoryCss, /\.e30-human-review/);
|
||||
assert.match(e30HumanReviewCss, /\.e30-human-review/);
|
||||
});
|
||||
|
||||
test("central composition files cannot silently become monoliths again", async () => {
|
||||
const ratchets = [
|
||||
["App.tsx", 1_250],
|
||||
["workspaces/Workspaces.tsx", 1_200],
|
||||
["workspaces/laboratory/LaboratoryArchiveWorkspace.tsx", 1_000],
|
||||
["core/laboratory/advancedResults.ts", 1_000],
|
||||
["core/laboratory/e40ProductGate.ts", 500],
|
||||
["styles/workspaces.css", 4_350],
|
||||
["styles/laboratory.css", 900],
|
||||
["styles/laboratory-reporting.css", 100],
|
||||
];
|
||||
|
||||
for (const [relativePath, maximumLines] of ratchets) {
|
||||
const lineCount = (await read(relativePath)).split("\n").length;
|
||||
assert.ok(
|
||||
lineCount <= maximumLines,
|
||||
`${relativePath} has ${lineCount} lines; split the feature instead of raising ${maximumLines}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ let server;
|
||||
let canonicalMapGravityLocalPointToBodyGround;
|
||||
let canonicalRecordedLabPackedTgsCells;
|
||||
let canonicalRecordedLabTgsIsCurrent;
|
||||
let resolveCanonicalLabReplay;
|
||||
|
||||
before(async () => {
|
||||
server = await createServer({
|
||||
@@ -20,6 +21,9 @@ before(async () => {
|
||||
canonicalRecordedLabPackedTgsCells,
|
||||
canonicalRecordedLabTgsIsCurrent,
|
||||
} = await server.ssrLoadModule("/src/core/laboratory/canonicalRecordedLab.ts"));
|
||||
({ resolveCanonicalLabReplay } = await server.ssrLoadModule(
|
||||
"/src/core/laboratory/canonicalLabReplay.ts",
|
||||
));
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
@@ -76,3 +80,55 @@ test("recorded LAB spatial loading is shared, profile-bound and experiment-neutr
|
||||
assert.doesNotMatch(scheduler, /RAVNOVES|vegetation|DDRNet/);
|
||||
assert.doesNotMatch(vegetation, /fetchCanonicalRecordedLabSpatialFrame|CanonicalRecordedLabSpatialFrame/);
|
||||
});
|
||||
|
||||
test("canonical LAB resolves one generation-bound merged RRD", async () => {
|
||||
const baseGeneration = "a".repeat(64);
|
||||
const replayGeneration = "b".repeat(64);
|
||||
const resultId = `lab-v1-vegetation-shadow-${"c".repeat(64)}`;
|
||||
let request;
|
||||
const replay = await resolveCanonicalLabReplay(resultId, {
|
||||
kind: "rerun-recording",
|
||||
sessionId: "session-001",
|
||||
sourceUrl: "/api/v1/observation-sessions/session-001/recording.rrd",
|
||||
viewerSourceUrl:
|
||||
`/api/v1/observation-sessions/session-001/recording.rrd?generation=${baseGeneration}`,
|
||||
mediaType: "application/vnd.rerun.rrd",
|
||||
timeline: "session_time",
|
||||
timelineStartSeconds: 0,
|
||||
timelineEndSeconds: 10,
|
||||
seekable: true,
|
||||
byteLength: 100,
|
||||
sha256: baseGeneration,
|
||||
playback: { speed: 1, loop: false },
|
||||
mediaSources: [],
|
||||
}, {
|
||||
origin: "http://mission-core.test",
|
||||
fetcher: async (url, options) => {
|
||||
request = { url, options };
|
||||
return new Response(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/vnd.rerun.rrd",
|
||||
"Content-Length": "234567",
|
||||
"ETag": `"${replayGeneration}"`,
|
||||
"X-Rerun-Format": "RRF2",
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const sourceUrl =
|
||||
`/api/v1/laboratory/vegetation-shadow/${resultId}/canonical-replay.rrd`;
|
||||
assert.equal(
|
||||
request.url,
|
||||
`http://mission-core.test${sourceUrl}?base_generation=${baseGeneration}`,
|
||||
);
|
||||
assert.equal(request.options.method, "HEAD");
|
||||
assert.deepEqual(replay, {
|
||||
sourceUrl,
|
||||
viewerSourceUrl: `${sourceUrl}?generation=${replayGeneration}`,
|
||||
byteLength: 234567,
|
||||
sha256: replayGeneration,
|
||||
blueprintSourceUrl: "/api/v1/observation-sessions/session-001/blueprint.rrd",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -411,6 +411,15 @@ test("recorded blueprint endpoint is derived only from canonical same-origin RRD
|
||||
),
|
||||
null,
|
||||
);
|
||||
assert.equal(
|
||||
resolveRecordedBlueprintUrl(
|
||||
`/api/v1/laboratory/vegetation-shadow/lab-v1-vegetation-shadow-${"a".repeat(64)}`
|
||||
+ "/canonical-replay.rrd",
|
||||
"http://127.0.0.1:5174",
|
||||
"/api/v1/observation-sessions/session-1/blueprint.rrd",
|
||||
),
|
||||
"http://127.0.0.1:5174/api/v1/observation-sessions/session-1/blueprint.rrd",
|
||||
);
|
||||
});
|
||||
|
||||
test("recorded replay becomes ready only after the complete declared timeline is buffered", () => {
|
||||
|
||||
@@ -69,6 +69,23 @@ test("only the canonical digest-bound generation URL reaches the native Rerun re
|
||||
}
|
||||
});
|
||||
|
||||
test("one canonical LAB replay generation reaches the same native receiver", () => {
|
||||
const labSource =
|
||||
`/api/v1/laboratory/vegetation-shadow/lab-v1-vegetation-shadow-${"c".repeat(64)}`
|
||||
+ "/canonical-replay.rrd";
|
||||
const descriptor = {
|
||||
sourceUrl: labSource,
|
||||
viewerSourceUrl: `${labSource}?generation=${sha256}`,
|
||||
byteLength: 300_000_000,
|
||||
sha256,
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
resolveRecordedViewerSourceUrl(descriptor, "http://mission-core.test"),
|
||||
`http://mission-core.test${descriptor.viewerSourceUrl}`,
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@@ -13,7 +13,6 @@ let isUsableRecordedPlaybackRange;
|
||||
let recordedPlaybackBufferState;
|
||||
let recordedPlaybackRangeWhenReady;
|
||||
let rerunPresentationStatus;
|
||||
let shouldReapplyRecordedBlueprint;
|
||||
|
||||
before(async () => {
|
||||
server = await createServer({
|
||||
@@ -31,7 +30,6 @@ before(async () => {
|
||||
recordedPlaybackBufferState,
|
||||
recordedPlaybackRangeWhenReady,
|
||||
rerunPresentationStatus,
|
||||
shouldReapplyRecordedBlueprint,
|
||||
} = await server.ssrLoadModule("/src/components/RerunViewport.tsx"));
|
||||
});
|
||||
|
||||
@@ -188,33 +186,6 @@ test("paused recorded replay seeks once to its first presentable frame", () => {
|
||||
assert.equal(gate.attempted(), true);
|
||||
});
|
||||
|
||||
test("LAB sidecar store reopen reapplies only its matching recorded blueprint", () => {
|
||||
const identity = {
|
||||
applicationId: "nodedc_mission_core_recorded",
|
||||
recordingId: "recording-001",
|
||||
};
|
||||
const event = {
|
||||
application_id: "nodedc_mission_core_recorded",
|
||||
recording_id: "recording-001",
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
shouldReapplyRecordedBlueprint(true, true, true, identity, event),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
shouldReapplyRecordedBlueprint(false, true, true, identity, event),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
shouldReapplyRecordedBlueprint(true, true, true, identity, {
|
||||
...event,
|
||||
recording_id: "recording-002",
|
||||
}),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("recorded autoplay starts at the first presentable camera frame without shrinking the range", () => {
|
||||
const gate = createRecordedAutoplayGate();
|
||||
const seeks = [];
|
||||
|
||||
@@ -481,25 +481,25 @@ test("vegetation realtime LAB uses one upstream Rerun clock and keeps archival r
|
||||
assert.match(m49Source, /controlLabel: "SEMANTICS"/);
|
||||
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, /КАНОНИЧЕСКАЯ ЗАПИСАННАЯ LAB · RAVNOVES004TREE/);
|
||||
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(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, /ИСХ\. ТОЧКИ/);
|
||||
assert.match(rerunSource, /ЛОК\. SLAM/);
|
||||
assert.match(rerunSource, /resolveCanonicalLabReplay/);
|
||||
assert.doesNotMatch(rerunSource, /canonical-overlay\.rrd/);
|
||||
assert.match(rerunSource, /unifiedPerception: splitView/);
|
||||
assert.doesNotMatch(rerunSource, /LaboratoryRecordedClipPlayer|LaboratoryMetricEvidenceScene/);
|
||||
assert.match(resultSource, /point-aligned 3D semantics пока не запечатаны/);
|
||||
assert.match(rerunSource, /value: "tgs", label: "TGS COSTMAP", disabled: true/);
|
||||
assert.match(rerunSource, /value: "tgs", label: "TGS", disabled: true/);
|
||||
assert.match(canonicalSource, /primary=\{mediaPane\}/);
|
||||
assert.match(canonicalSource, /secondary=\{spatialPane/);
|
||||
assert.match(canonicalSource, /missioncore\.canonical-recorded-lab-replay\/v1/);
|
||||
assert.match(canonicalSource, /separatorLabel="Изменить размер VIDEO\/CAMERA и 3D\/PLAN"/);
|
||||
assert.match(canonicalSource, /separatorLabel="Изменить размер видео\/камеры и 3D\/плана"/);
|
||||
assert.match(resultSource, /linked canonical M4\.9 TGS evidence/);
|
||||
assert.match(resultSource, /linkedTgsResultId/);
|
||||
assert.match(benchmarkSource, /M48MaskComparisonVisual/);
|
||||
|
||||
Reference in New Issue
Block a user