feat(planning): consolidate recorded-route localization and spatial scene

Preserve the completed teach-and-repeat laboratory stage: reference preparation, cascaded acquisition, local tracking and recovery, recording lifecycle, replay qualification, and persistent Rerun scene controls. Document the open grid-picking regression and Rerun upgrade contract. No autonomous driving or loop-closure optimization is claimed.
This commit is contained in:
DCCONSTRUCTIONS
2026-09-21 08:47:19 +03:00
parent be58d589e2
commit e515ab1b8c
189 changed files with 19074 additions and 758 deletions
+11 -7
View File
@@ -7,22 +7,26 @@ export function SpatialScene({viewportRef, focused, primaryFocused, mediaMaximiz
navigationReady=false}: {
viewportRef: RefObject<HTMLDivElement|null>; focused?:boolean; primaryFocused?:boolean;
mediaMaximized?:boolean; toolbar:ReactNode; renderer:ReactNode; deviceControls?:ReactNode;
sourceControls?:ReactNode; status:{label:string;tone:'neutral'|'success'|'warning'|'danger';message?:string};
sourceControls?:ReactNode; status:{label:string;tone:'neutral'|'success'|'warning'|'danger';message?:string;pulse?:boolean};
metrics:ReactNode; timeline?:ReactNode; media?:ReactNode; overlays?:ReactNode; footer?:ReactNode;
navigationReady?:boolean;
}) {
const detailsHidden = primaryFocused || mediaMaximized;
return <div className="spatial-workspace" data-focused={focused?'true':undefined}>
<div className="spatial-toolbar" data-viewer-controls="v1"><div className="spatial-toolbar__actions">{toolbar}</div></div>
<div ref={viewportRef} className="spatial-viewport-shell" data-primary-focused={primaryFocused?'true':undefined} data-media-maximized={mediaMaximized?'true':undefined}>
{renderer}
{deviceControls&&<div className="scene-device-controls">{deviceControls}</div>}
{sourceControls}
<div className="scene-status scene-status--top-left" aria-hidden={primaryFocused||mediaMaximized}>
<span className="section-eyebrow">ВИЗУАЛЬНЫЙ ДВИЖОК</span>
<StatusBadge tone={status.tone}>{status.label}</StatusBadge>
{status.message?<small>{status.message}</small>:null}
{detailsHidden ? sourceControls : null}
<div className="scene-information" aria-hidden={detailsHidden}>
{!detailsHidden ? sourceControls : null}
<div className="scene-status">
<span className="section-eyebrow">ВИЗУАЛЬНЫЙ ДВИЖОК</span>
<StatusBadge tone={status.tone} pulse={status.pulse}>{status.label}</StatusBadge>
{status.message?<small>{status.message}</small>:null}
</div>
<div className="scene-metrics" aria-label="Метрики пространственной сцены">{metrics}</div>
</div>
<div className="scene-metrics" aria-label="Метрики пространственной сцены" aria-hidden={primaryFocused||mediaMaximized}>{metrics}</div>
{overlays}
{navigationReady&&!mediaMaximized&&<div className="scene-navigation-hint" aria-label="Навигация по 3D-сцене"><span>Колесо · зум к курсору</span><span>WASD · свободный проход</span></div>}
{timeline}{media}
@@ -1,8 +1,8 @@
import {Button,Icon} from '@nodedc/ui-react';
export function SpatialToolbarActions({openSource,openLayers,openDisplay}:{openSource:()=>void;openLayers:()=>void;openDisplay:()=>void}) {
export function SpatialToolbarActions({openSource,openLayers,openDisplay,activeTool}:{openSource?:()=>void;openLayers:()=>void;openDisplay:()=>void;activeTool?:'layers'|'display'|null}) {
return <>
<Button size="compact" variant="secondary" icon={<Icon name="network"/>} onClick={openSource}>Движок</Button>
<Button size="compact" variant="secondary" icon={<Icon name="list"/>} onClick={openLayers}>Слои</Button>
<Button size="compact" variant="secondary" icon={<Icon name="sliders"/>} onClick={openDisplay}>Отображение</Button>
{openSource&&<Button size="compact" variant="secondary" icon={<Icon name="network"/>} onClick={openSource}>Движок</Button>}
<Button size="compact" variant="secondary" icon={<Icon name="list"/>} aria-pressed={activeTool==='layers'} onClick={openLayers}>Слои</Button>
<Button size="compact" variant="secondary" icon={<Icon name="sliders"/>} aria-pressed={activeTool==='display'} onClick={openDisplay}>Отображение</Button>
</>;
}
-14
View File
@@ -86,15 +86,6 @@
right: 0.85rem;
}
.scene-status--top-left {
left: 7.1rem;
}
.scene-status[aria-hidden="true"],
.scene-metrics[aria-hidden="true"] {
display: none;
}
.observation-source-menu {
overflow: hidden;
font-family: var(--nodedc-font-family);
@@ -634,11 +625,6 @@ i[data-availability="error"] {
}
@media (max-width: 760px) {
.scene-status--top-left {
top: 4.2rem;
left: 0.6rem;
}
.observation-timeline[data-accumulation="true"] {
grid-template-columns: minmax(0, 1fr);
}
+27 -7
View File
@@ -261,8 +261,6 @@
line-height: 1.5;
}
.scene-status,
.scene-metrics,
.scene-adapter-note,
.scene-selection,
.scene-timeline {
@@ -273,13 +271,34 @@
backdrop-filter: blur(16px);
}
.scene-status--top-left {
.scene-information {
position: absolute;
z-index: 12;
top: 0.85rem;
left: 0.85rem;
max-width: min(20rem, calc(100% - 1.7rem));
display: grid;
justify-items: start;
gap: 0.65rem;
pointer-events: none;
}
.scene-information[aria-hidden="true"] { display: none; }
.scene-information > .scene-source-controls { position: static; pointer-events: auto; }
.scene-status,
.scene-metrics {
border: 0;
background: rgb(9 10 13 / 0.74);
backdrop-filter: blur(16px);
border-radius: 0.9rem;
max-width: 100%;
}
.scene-status {
display: grid;
justify-items: start;
gap: 0.42rem;
border-radius: 0.9rem;
padding: 0.7rem;
}
@@ -291,11 +310,8 @@
}
.scene-metrics {
top: 0.85rem;
right: 0.85rem;
display: grid;
min-width: 10rem;
border-radius: 0.9rem;
padding: 0.35rem 0.75rem;
}
@@ -313,6 +329,10 @@
.scene-metrics strong { color: var(--nodedc-text-primary); font-size: 0.72rem; }
.scene-metrics small { color: var(--nodedc-text-muted); font-size: 0.52rem; font-weight: 500; }
@media (max-width: 760px) {
.scene-information { top: 0.6rem; left: 0.6rem; max-width: min(20rem, calc(100% - 1.2rem)); }
}
.scene-adapter-note {
top: 50%;
left: 50%;