feat(lab): visualize semantic SLAM shadow

This commit is contained in:
DCCONSTRUCTIONS
2026-08-06 11:26:46 +03:00
parent 8eaa3ab497
commit 81ae4425cb
18 changed files with 1747 additions and 42 deletions
@@ -39,6 +39,7 @@ import { E46GRectifiedDetectorBakeoffResultView } from "./E46GRectifiedDetectorB
import { E46HFullRectifiedFrontReplayResultView } from "./E46HFullRectifiedFrontReplayResult";
import { E46IGroundingDinoFullReplayResultView } from "./E46IGroundingDinoFullReplayResult";
import { E46JRawFisheyeRealtimeResultView } from "./E46JRawFisheyeRealtimeResult";
import { E47SemanticSlamResultView } from "./E47SemanticSlamResult";
import { M4ReplayThreatResultView } from "./M4ReplayThreatResult";
export { isAdvancedLaboratoryWorkId };
@@ -85,6 +86,9 @@ export function AdvancedLaboratoryResult({
if (workId === "m4-replay-threat" && results.m4Threat) {
return <M4ReplayThreatResultView rigLabel={rigLabel} result={results.m4Threat} />;
}
if (workId === "e47-semantic-slam-shadow" && results.e47) {
return <E47SemanticSlamResultView rigLabel={rigLabel} result={results.e47} />;
}
if (workId === "l3-pointpillars-visual-audit" && results.l3) {
return <L3PointPillarsResult result={results.l3} />;
}
@@ -0,0 +1,156 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { E47SemanticSlamResult } from "../../core/laboratory/e47SemanticSlam";
import { formatNumber } from "../../presentation";
import { M4ReplayThreatVisual } from "./M4ReplayThreatVisual";
export function E47SemanticSlamResultView({
rigLabel,
result,
}: {
rigLabel: string;
result: E47SemanticSlamResult;
}) {
const pointProjectedCoverage = result.metrics.points.total
? result.metrics.points.projected / result.metrics.points.total
: 0;
const pointLabeledCoverage = result.metrics.points.total
? result.metrics.points.labeled / result.metrics.points.total
: 0;
const observationLabeledCoverage = result.metrics.observations.total
? result.metrics.observations.labeled / result.metrics.observations.total
: 0;
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="E47 · semantic mask → KB4 → SLAM shadow"
description="Зафиксированные EoMT-маски проецируются заводской KB4-калибровкой на исходные точки SLAM/LiDAR и отдельно агрегируются по уже существующим геометрическим наблюдениям. Это диагностический слой: он не меняет occupancy, motion, threat или safe/unknown."
status="Diagnostic contract passed · provider quality gate open"
statusTone="warning"
facts={[
{
label: "Конфигурация",
value: `${rigLabel} · RIGHT camera + registered SLAM cloud · recorded replay`,
},
{
label: "Semantic control",
value: `${result.provider.modelId} · exact revision ${result.provider.modelRevision.slice(0, 12)}`,
},
{
label: "Проекция",
value: `factory KB4 · ${result.calibrationContentSha256.slice(0, 12)} · frame-local point IDs`,
},
{
label: "Синхрон",
value: `semantic↔camera exact ledger · camera↔LiDAR E6 best-effort ≤${formatNumber(result.temporalBinding.maximumLidarCameraDeltaMs, 0)} ms · HW sync: нет`,
},
{
label: "Покрытие",
value: `${result.metrics.frames.maskAvailable}/${result.metrics.frames.total} masks · ${formatNumber(pointProjectedCoverage * 100, 1)}% projected · ${formatNumber(pointLabeledCoverage * 100, 1)}% unambiguous`,
},
{
label: "Визуал",
value: "4489-frame VIDEO/CAMERA/3D/PLAN · один recorded clock · semantic layer",
},
]}
brief={{
question: "Можно ли добавить плотную семантику камеры к сильной SLAM/LiDAR-геометрии, не превратив классификацию в источник ложного свободного пространства?",
approach: "Для всех 4489 кадров переиспользованы неизменяемые EoMT masks, factory KB4 extrinsic/intrinsic и тот же source point index space, на котором построен M4.6. Semantic↔camera сверяется fail-closed по sequence и session-time; camera↔LiDAR сохраняет исходный bounded nearest-arrival E6 contract, а не выдаётся за hardware-sync. Каждая точка получает labeled, ambiguous, unprojected или absent.",
principalResult: `${result.metrics.points.labeled.toLocaleString("ru-RU")} точек получили однозначный класс, ${result.metrics.points.ambiguous.toLocaleString("ru-RU")} остались semantic-ambiguous, ${result.metrics.points.unprojected.toLocaleString("ru-RU")} не спроецировались. Из ${result.metrics.observations.total.toLocaleString("ru-RU")} неизменённых geometry observations однозначный класс получили ${formatNumber(observationLabeledCoverage * 100, 1)}%.`,
limitation: "EoMT здесь — фиксированный control provider, а не выбранная production-модель. Physical camera↔LiDAR hardware-sync не доказан; принят только E6 nearest-host-arrival best-effort в пределах 100 мс. Semantic/instance truth, obstacle recall и fisheye-specific качество независимо не размечены; отсутствие класса никогда не означает free.",
}}
method={{
completeness: "complete",
executionClass: "hybrid",
pipelineId: "semantic-slam-diagnostic-shadow/v1",
components: [
{
kind: "source",
name: result.semanticResultId,
version: result.provider.modelRevision,
role: "sealed full-route uint8 semantic masks",
identitySha256: result.provider.modelWeightsSha256,
},
{
kind: "source",
name: result.sourcePackId,
version: "registered map increments + vendor SLAM pose",
role: "точный frame-local point index space",
identitySha256: result.sourcePackId.split("-").at(-1) ?? null,
},
{
kind: "source",
name: result.geometryResultId,
version: "immutable M4 geometry observations",
role: "неизменяемые obstacle IDs, occupancy и metric geometry",
identitySha256: result.geometryResultId.split("-").at(-1) ?? null,
},
{
kind: "algorithm",
name: "semantic diagnostic fusion",
version: result.profileId,
role: "KB4 mask projection + point/observation accounting без safety authority",
identitySha256: result.resultId.split("-").at(-1) ?? null,
},
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="E47 VISUAL EVIDENCE · VIDEO / CAMERA / 3D / PLAN"
title="Синхронный контроль маски, semantic-точек, геометрии и коридора"
kind="diagnostic-model"
resizable
>
<M4ReplayThreatVisual
resultId={result.baseM4ResultId}
semantic={{
resultId: result.resultId,
taxonomy: result.taxonomy,
}}
/>
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Semantic/SLAM seam принят; качество provider ещё не принято"
status="Жёлтый: артефакты и проекция доказаны, independent semantic truth отсутствует"
statusTone="warning"
metrics={[
{
label: "Semantic masks",
value: `${result.metrics.frames.maskAvailable}/${result.metrics.frames.total}`,
hint: "exact immutable full-route archive",
},
{
label: "Point labels",
value: result.metrics.points.labeled.toLocaleString("ru-RU"),
hint: `${result.metrics.points.unprojected.toLocaleString("ru-RU")} unprojected · ${result.metrics.points.ambiguous.toLocaleString("ru-RU")} ambiguous`,
},
{
label: "Observation labels",
value: result.metrics.observations.labeled.toLocaleString("ru-RU"),
hint: `${result.metrics.observations.ambiguous.toLocaleString("ru-RU")} ambiguous`,
},
{
label: "Derivative build",
value: `${formatNumber(result.metrics.runtime.framesPerSecond, 1)} FPS`,
hint: `${formatNumber(result.metrics.runtime.elapsedMs / 1000, 1)} s offline`,
},
]}
conclusion={{
proved: "Одна каноническая модель-независимая форма принимает sealed semantic mask, привязывает её к исходному кадру и к factory KB4, маркирует полный frame-local point space и публикует проверяемое semantic evidence для существующих geometry observations. Текущий M4.6 при этом не изменён.",
notProved: "Не доказаны physical hardware-sync, class accuracy, instance separation, удержание отдельных объектов, obstacle recall, перенос на другой маршрут/provider и production latency на Worker 006. Semantic evidence не имеет navigation/safety authority.",
decision: "Оставить EoMT как контрольную ветку. Следующий честный A/B — NVIDIA CitySemSegFormer на замороженном truth-island через тот же provider contract; после ручного GT сравнивать качество, а не интерфейс или цвет overlay.",
}}
/>
)}
/>
);
}
@@ -9,11 +9,20 @@ import {
} from "../../components/laboratory/LaboratoryMetricEvidenceScene";
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
import { RecordedEvidenceImageScene } from "../../components/laboratory/RecordedEvidenceImageScene";
import type {
RecordedEvidenceSemanticClass,
RecordedEvidenceSemanticOverlay,
RecordedEvidenceSemanticPaletteEntry,
} from "../../components/laboratory/RecordedEvidenceSemanticMaskOverlay";
import {
RecordedEvidenceVideoScene,
type RecordedEvidenceBox,
} from "../../components/laboratory/RecordedEvidenceVideoScene";
import { useRecordedEvidencePlayback } from "../../components/laboratory/useRecordedEvidencePlayback";
import {
e47SemanticMaskUrl,
type E47SemanticClass,
} from "../../core/laboratory/e47SemanticSlam";
import type {
M4ThreatCameraProposal,
M4ThreatTimelineFrame,
@@ -26,6 +35,7 @@ import {
useM4ThreatTimelineFrame,
useM4ThreatTimelineMetadata,
} from "./useM4ThreatTimeline";
import { useE47SemanticTimelineFrame } from "./useE47SemanticTimeline";
type M4ThreatViewMode = "video" | "camera" | LaboratoryMetricSceneMode;
@@ -65,12 +75,24 @@ function SpatialState({ message: text }: { message: string }) {
);
}
export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
export interface M4ReplayThreatSemanticLayer {
resultId: string;
taxonomy: readonly E47SemanticClass[];
}
export function M4ReplayThreatVisual({
resultId,
semantic,
}: {
resultId: string;
semantic?: M4ReplayThreatSemanticLayer;
}) {
const [mode, setMode] = useState<M4ThreatViewMode>("video");
const [spatialMode, setSpatialMode] = useState<LaboratoryMetricSceneMode>("3d");
const [showCurrentIncrement, setShowCurrentIncrement] = useState(true);
const [showLocalSurface, setShowLocalSurface] = useState(true);
const [showRollingMap, setShowRollingMap] = useState(true);
const [showSemantic, setShowSemantic] = useState(true);
const [expanded, setExpanded] = useState(false);
const metricSceneRef = useRef<LaboratoryMetricEvidenceSceneHandle | null>(null);
const metadata = useM4ThreatTimelineMetadata(resultId);
@@ -140,6 +162,12 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
}, [resultId]);
if (timelineFrame.activeFrame) lastFrameRef.current = timelineFrame.activeFrame;
const frame = timelineFrame.activeFrame ?? lastFrameRef.current;
const semanticTimeline = useE47SemanticTimelineFrame({
resultId: semantic?.resultId ?? null,
activeSequence: frame?.sequence ?? timelineFrame.activeSequence,
frameCount: metadata.timeline?.frameCount ?? 0,
taxonomy: semantic?.taxonomy ?? [],
});
const displayingBufferedFrame = Boolean(
frame
&& timelineFrame.activeSequence !== null
@@ -173,6 +201,57 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
pointLimit: 20_000,
},
), [frame, metadata.timeline, timelineFrame.availableFrames]);
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
() => semantic?.taxonomy.map((item) => ({
id: item.classId,
label: `semantic: ${item.label}`,
})) ?? [],
[semantic?.taxonomy],
);
const semanticPalette = useMemo<readonly RecordedEvidenceSemanticPaletteEntry[]>(
() => semantic?.taxonomy.map((item) => ({
classId: item.classId,
color: item.disposition === "ambiguous"
? { kind: "token" as const, token: "--nodedc-warning-rgb" as const }
: { kind: "diagnostic" as const, rgb: item.colorRgb },
opacity: item.disposition === "ambiguous" ? 0.22 : 0.56,
})) ?? [],
[semantic?.taxonomy],
);
const semanticFrame = semanticTimeline.activeFrame?.sequence === frame?.sequence
? semanticTimeline.activeFrame
: null;
const semanticIntegrityError = semantic && frame?.spatialAvailable && semanticFrame && (
semanticFrame.sourcePointCount !== frame.pointCloudSourceCount
|| frame.pointCloudSampleCount !== frame.pointCloudSourceCount
|| frame.pointCloudBodyXyzM.length !== frame.pointCloudSourceCount
)
? "E47 semantic point index space не совпал с exact current increment M4.6."
: null;
const alignedSemanticPointIds = useMemo<readonly (number | null)[] | undefined>(() => {
if (
!semantic
|| !showSemantic
|| !frame
|| !frame.spatialAvailable
|| !semanticFrame
|| semanticIntegrityError
) return undefined;
return semanticFrame.classIds.map((classId, index) => {
const status = semanticFrame.statusCodes[index];
return status === 2 || status === 3 ? classId : null;
});
}, [frame, semantic, semanticFrame, semanticIntegrityError, showSemantic]);
const semanticOverlay: RecordedEvidenceSemanticOverlay | undefined =
semantic && showSemantic && frame
? {
src: e47SemanticMaskUrl(semantic.resultId, frame.sequence),
classes: semanticClasses,
palette: semanticPalette,
opacity: 0.48,
ariaLabel: `E47 semantic mask frame ${frame.sequence + 1}`,
}
: undefined;
const seek = (seconds: number) => playbackController.seek(seconds);
const handleModeChange = (next: M4ThreatViewMode) => {
@@ -205,40 +284,55 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
<Icon name="chevron-right" size={16} />
</IconButton>
</div>
{mode === "3d" || mode === "plan" ? (
{mode === "3d" || mode === "plan" || semantic ? (
<div
className="nodedc-segmented m4-replay-threat-visual__layer-controls"
role="group"
aria-label="Слои пространственного evidence"
>
<button
type="button"
className="nodedc-segmented__item"
data-active={showCurrentIncrement ? "true" : undefined}
aria-pressed={showCurrentIncrement}
onClick={() => setShowCurrentIncrement((visible) => !visible)}
>
CURRENT
</button>
<button
type="button"
className="nodedc-segmented__item"
data-active={showLocalSurface ? "true" : undefined}
aria-pressed={showLocalSurface}
title="Bounded local SLAM surface · visual-derived"
onClick={() => setShowLocalSurface((visible) => !visible)}
>
LOCAL SLAM
</button>
<button
type="button"
className="nodedc-segmented__item"
data-active={showRollingMap ? "true" : undefined}
aria-pressed={showRollingMap}
onClick={() => setShowRollingMap((visible) => !visible)}
>
ROLLING
</button>
{mode === "3d" || mode === "plan" ? (
<>
<button
type="button"
className="nodedc-segmented__item"
data-active={showCurrentIncrement ? "true" : undefined}
aria-pressed={showCurrentIncrement}
onClick={() => setShowCurrentIncrement((visible) => !visible)}
>
CURRENT
</button>
<button
type="button"
className="nodedc-segmented__item"
data-active={showLocalSurface ? "true" : undefined}
aria-pressed={showLocalSurface}
title="Bounded local SLAM surface · visual-derived"
onClick={() => setShowLocalSurface((visible) => !visible)}
>
LOCAL SLAM
</button>
<button
type="button"
className="nodedc-segmented__item"
data-active={showRollingMap ? "true" : undefined}
aria-pressed={showRollingMap}
onClick={() => setShowRollingMap((visible) => !visible)}
>
ROLLING
</button>
</>
) : null}
{semantic ? (
<button
type="button"
className="nodedc-segmented__item"
data-active={showSemantic ? "true" : undefined}
aria-pressed={showSemantic}
onClick={() => setShowSemantic((visible) => !visible)}
>
SEMANTICS
</button>
) : null}
</div>
) : null}
</div>
@@ -274,6 +368,9 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
{frame.spatialAvailable
? `${frame.pointCloudSampleCount}/${frame.pointCloudSourceCount} exact · ${localSurface.pointsBodyXyzM.length} local SLAM / ${localSurface.sourceFrameCount} frames`
: "body frame / current increment unavailable"}
{semantic && semanticFrame
? ` · semantic L ${semanticFrame.counts.labeled} · A ${semanticFrame.counts.ambiguous} · U ${semanticFrame.counts.unprojected} · Ø ${semanticFrame.counts.absent}`
: semantic ? " · semantic buffer" : ""}
</small>
</div>
<div>
@@ -314,6 +411,7 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
imageWidth={timeline.imageWidth}
imageHeight={timeline.imageHeight}
boxes={activeBoxes}
semanticOverlay={mode === "video" ? semanticOverlay : undefined}
ariaLabel={`M4.6 recorded-realtime frame ${frame?.sequence ?? 0}: ${activeBoxes.length} proposals`}
interactive={false}
/>
@@ -337,6 +435,7 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
imageWidth={timeline.imageWidth}
imageHeight={timeline.imageHeight}
boxes={activeBoxes}
semanticOverlay={mode === "camera" ? semanticOverlay : undefined}
ariaLabel={`M4.6 exact camera frame ${frame.sequence}: ${activeBoxes.length} proposals`}
/>
) : null}
@@ -360,6 +459,9 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
showCurrentIncrement={showCurrentIncrement}
showLocalSurface={showLocalSurface}
showRollingMap={showRollingMap}
pointSemanticClassIds={alignedSemanticPointIds}
semanticClasses={semanticClasses}
semanticPalette={semanticPalette}
/>
) : null}
</div>
@@ -375,6 +477,24 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
<span>{timelineFrame.error}</span>
</div>
) : null}
{semantic && semanticTimeline.loading ? (
<div className="m4-replay-threat-visual__buffering" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Догружаем semantic-point evidence E47</span>
</div>
) : null}
{semanticTimeline.error ? (
<div className="m4-replay-threat-visual__buffering" role="alert">
<Icon name="alert" size={16} />
<span>{semanticTimeline.error}</span>
</div>
) : null}
{semanticIntegrityError ? (
<div className="m4-replay-threat-visual__buffering" role="alert">
<Icon name="alert" size={16} />
<span>{semanticIntegrityError}</span>
</div>
) : null}
{frame && !frame.spatialAvailable && (mode === "3d" || mode === "plan") ? (
<div className="m4-replay-threat-visual__buffering" role="status">
На этом кадре нет квалифицированного body frame; сцена сохранена.
@@ -409,7 +529,9 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
return (
<div className="l3-visual-audit m4-replay-threat-visual">
<LaboratoryEvidenceViewer
label="M4.6 dual-evidence recorded-realtime replay"
label={semantic
? "E47 semantic + SLAM diagnostic replay"
: "M4.6 dual-evidence recorded-realtime replay"}
className="m4-replay-threat-evidence-viewer"
mode={mode}
modes={[
@@ -64,6 +64,13 @@ const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`
experimentName: "RAVNOVES00 dual-evidence threat qualification",
variantName: "M4.6 · virtual corridor replay · VIDEO/CAMERA/3D",
},
"e47-semantic-slam-shadow": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Camera + LiDAR dual evidence`,
experimentId: "ravnoves00-semantic-slam-shadow-r1",
experimentName: "RAVNOVES00 semantic mask → KB4 → SLAM diagnostic shadow",
variantName: "E47 · EoMT control · full semantic point projection",
},
"e28-local-surface": {
profileId: "rig-camera-local-surface-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Camera-first + local-surface LiDAR`,
@@ -43,6 +43,7 @@ function mergeResults(
e46h: next.e46h ?? current.e46h,
e46i: next.e46i ?? current.e46i,
e46j: next.e46j ?? current.e46j,
e47: next.e47 ?? current.e47,
l34: next.l34 ?? current.l34,
l34a: next.l34a ?? current.l34a,
l34b: next.l34b ?? current.l34b,
@@ -0,0 +1,113 @@
import { useEffect, useMemo, useRef, useState } from "react";
import {
fetchE47SemanticTimelineChunk,
type E47SemanticClass,
type E47SemanticTimelineChunk,
type E47SemanticTimelineFrame,
} from "../../core/laboratory/e47SemanticSlam";
const CHUNK_SIZE = 24;
const RETAINED_CHUNK_COUNT = 8;
const PREFETCH_CHUNKS_AHEAD = 2;
function chunkWindowStarts(activeStart: number, frameCount: number): readonly number[] {
return Array.from(
{ length: PREFETCH_CHUNKS_AHEAD + 2 },
(_, index) => activeStart + (index - 1) * CHUNK_SIZE,
).filter((start) => start >= 0 && start < frameCount);
}
function errorMessage(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "Semantic point evidence E47 недоступен.";
}
export function useE47SemanticTimelineFrame({
resultId,
activeSequence,
frameCount,
taxonomy,
}: {
resultId: string | null;
activeSequence: number | null;
frameCount: number;
taxonomy: readonly E47SemanticClass[];
}) {
const [chunks, setChunks] = useState<ReadonlyMap<number, E47SemanticTimelineChunk>>(
() => new Map(),
);
const [error, setError] = useState<string | null>(null);
const chunksRef = useRef(chunks);
const inFlight = useRef(new Map<number, AbortController>());
const activeStartRef = useRef<number | null>(null);
chunksRef.current = chunks;
useEffect(() => {
for (const controller of inFlight.current.values()) controller.abort();
inFlight.current.clear();
const empty = new Map<number, E47SemanticTimelineChunk>();
chunksRef.current = empty;
setChunks(empty);
setError(null);
return () => {
for (const controller of inFlight.current.values()) controller.abort();
inFlight.current.clear();
};
}, [resultId]);
const activeStart = activeSequence === null
? null
: Math.floor(activeSequence / CHUNK_SIZE) * CHUNK_SIZE;
activeStartRef.current = activeStart;
useEffect(() => {
if (!resultId || activeStart === null || frameCount < 1) return;
for (const start of chunkWindowStarts(activeStart, frameCount)) {
if (chunksRef.current.has(start) || inFlight.current.has(start)) continue;
const controller = new AbortController();
inFlight.current.set(start, controller);
void fetchE47SemanticTimelineChunk(resultId, start, CHUNK_SIZE, {
signal: controller.signal,
taxonomy,
})
.then((chunk) => {
if (controller.signal.aborted) return;
setChunks((current) => {
const next = new Map(current);
next.set(start, chunk);
const center = activeStartRef.current ?? start;
const retained = [...next.keys()]
.sort((left, right) => Math.abs(left - center) - Math.abs(right - center))
.slice(0, RETAINED_CHUNK_COUNT);
const bounded = new Map(retained.map((key) => [key, next.get(key)!]));
chunksRef.current = bounded;
return bounded;
});
if (start === activeStartRef.current) setError(null);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted && start === activeStartRef.current) {
setError(errorMessage(caught));
}
})
.finally(() => {
if (inFlight.current.get(start) === controller) inFlight.current.delete(start);
});
}
}, [activeStart, frameCount, resultId, taxonomy]);
const activeFrame: E47SemanticTimelineFrame | null = useMemo(() => {
if (activeSequence === null || activeStart === null) return null;
return chunks.get(activeStart)?.frames.find(
(frame) => frame.sequence === activeSequence,
) ?? null;
}, [activeSequence, activeStart, chunks]);
return {
activeFrame,
loading: Boolean(resultId) && activeSequence !== null && !activeFrame && !error,
error,
};
}