feat(lab): add bounded local SLAM surface

This commit is contained in:
DCCONSTRUCTIONS
2026-08-06 09:41:25 +03:00
parent b8fb4ebcba
commit b7a51e26e6
10 changed files with 369 additions and 8 deletions
@@ -18,6 +18,7 @@ import type {
M4ThreatCameraProposal,
M4ThreatTimelineFrame,
} from "../../core/laboratory/m4ReplayThreat";
import { buildM4LocalSurface } from "../../core/laboratory/m4LocalSurface";
import { recordedObservationSources } from "../../core/observation/recordedObservationSources";
import { replayObservationSession } from "../../core/observation/sessionArchive";
import type { ObservationSourceDescriptor } from "../../core/runtime/contracts";
@@ -68,6 +69,7 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
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 [expanded, setExpanded] = useState(false);
const metricSceneRef = useRef<LaboratoryMetricEvidenceSceneHandle | null>(null);
@@ -161,6 +163,16 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
.map((item) => item.assessment.closestApproachM)
.filter((value): value is number => value !== null)
.sort((left, right) => left - right)[0] ?? null;
const localSurface = useMemo(() => buildM4LocalSurface(
timelineFrame.availableFrames,
frame,
metadata.timeline?.localSurfaceVisualization ?? {
windowSeconds: 2,
voxelSizeM: 0.1,
radiusM: 12,
pointLimit: 20_000,
},
), [frame, metadata.timeline, timelineFrame.availableFrames]);
const seek = (seconds: number) => playbackController.seek(seconds);
const handleModeChange = (next: M4ThreatViewMode) => {
@@ -206,7 +218,17 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
aria-pressed={showCurrentIncrement}
onClick={() => setShowCurrentIncrement((visible) => !visible)}
>
CURRENT INCREMENT
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"
@@ -215,7 +237,7 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
aria-pressed={showRollingMap}
onClick={() => setShowRollingMap((visible) => !visible)}
>
ROLLING MAP
ROLLING
</button>
</div>
) : null}
@@ -250,7 +272,7 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
</strong>
<small>
{frame.spatialAvailable
? `${frame.pointCloudSampleCount}/${frame.pointCloudSourceCount} exact lio_pcl increment`
? `${frame.pointCloudSampleCount}/${frame.pointCloudSourceCount} exact · ${localSurface.pointsBodyXyzM.length} local SLAM / ${localSurface.sourceFrameCount} frames`
: "body frame / current increment unavailable"}
</small>
</div>
@@ -328,13 +350,15 @@ export function M4ReplayThreatVisual({ resultId }: { resultId: string }) {
<LaboratoryMetricEvidenceScene
ref={metricSceneRef}
pointCloudBodyXyzM={frame.pointCloudBodyXyzM}
localSurfaceBodyXyzM={localSurface.pointsBodyXyzM}
obstacles={sceneObstacles}
rig={timeline.rig}
corridor={timeline.corridor}
occupiedVoxelSizeM={timeline.occupiedVoxelSizeM}
mode={spatialMode}
label="M4.6 recorded-realtime current increment and rolling occupancy"
label="M4.6 exact current increment, bounded local SLAM surface and rolling occupancy"
showCurrentIncrement={showCurrentIncrement}
showLocalSurface={showLocalSurface}
showRollingMap={showRollingMap}
/>
) : null}
@@ -146,10 +146,18 @@ export function useM4ThreatTimelineFrame({
(frame) => frame.sequence === activeSequence,
) ?? null;
}, [activeChunkStart, activeSequence, chunks]);
const availableFrames = useMemo(() => {
const unique = new Map<number, M4ThreatTimelineFrame>();
for (const chunk of chunks.values()) {
for (const frame of chunk.frames) unique.set(frame.sequence, frame);
}
return [...unique.values()].sort((left, right) => left.sequence - right.sequence);
}, [chunks]);
return {
activeSequence,
activeFrame,
availableFrames,
loading: error === null && Boolean(timeline) && !activeFrame,
error,
};