feat(perception): add autonomous vegetation shadow lab

This commit is contained in:
DCCONSTRUCTIONS
2026-08-27 23:26:35 +03:00
parent 7594c71dd1
commit 57204b3b0b
26 changed files with 2494 additions and 5 deletions
@@ -48,8 +48,10 @@ import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
import { fetchM49TgsFailClosedResult } from "./m49TgsFailClosed";
import { fetchM49TgsFullShadowResult } from "./m49TgsFullShadow";
import { fetchVegetationShadowResult } from "./vegetationShadow";
export type AdvancedLaboratoryWorkId =
| "lab-v1-vegetation-shadow"
| "m48-object-centric-quality"
| "m48-small-static-passage-regression"
| "m48-static-occupancy-qualification"
@@ -100,6 +102,7 @@ export interface AdvancedLaboratoryIndexItem {
}
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
"lab-v1-vegetation-shadow",
"m48-object-centric-quality",
"m48-small-static-passage-regression",
"m48-static-occupancy-qualification",
@@ -145,6 +148,7 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
];
const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
"lab-v1-vegetation-shadow": "lab-v1-vegetation-shadow",
"m48-object-centric-quality": "m48-object-quality-(?:pack|result)",
"m48-small-static-passage-regression": "m48-small-static-passage-regression",
"m48-static-occupancy-qualification": "m48-static-occupancy-qualification",
@@ -197,6 +201,7 @@ export function isAdvancedLaboratoryWorkId(
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
return {
vegetationShadow: null,
m47Graph: null,
m48: null,
m48SmallStatic: null,
@@ -330,7 +335,8 @@ export function advancedLaboratoryResultAvailable(
workId: AdvancedLaboratoryWorkId,
results: AdvancedLaboratoryResults,
): boolean {
return workId === "m48-object-centric-quality" ? results.m48 !== null
return workId === "lab-v1-vegetation-shadow" ? results.vegetationShadow !== null
: workId === "m48-object-centric-quality" ? results.m48 !== null
: workId === "m48-small-static-passage-regression" ? results.m48SmallStatic !== null
: workId === "m48-static-occupancy-qualification" ? results.m48StaticOccupancy !== null
: workId === "m48r3-static-occupancy-shadow" ? results.m48r3StaticOccupancy !== null
@@ -387,7 +393,10 @@ export async function fetchAdvancedLaboratoryResult(
} = {},
): Promise<AdvancedLaboratoryResults> {
const results = emptyAdvancedLaboratoryResults();
if (workId === "m48-object-centric-quality") {
if (workId === "lab-v1-vegetation-shadow") {
if (!resultId) throw new AdvancedLaboratoryContractError("Vegetation LAB identity не выбрана.");
results.vegetationShadow = await fetchVegetationShadowResult(resultId, { fetcher, signal });
} else if (workId === "m48-object-centric-quality") {
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8 lifecycle evidence identity не выбрана.");
results.m48 = await fetchM48LifecycleResult(resultId, { fetcher, signal });
} else if (workId === "m48-small-static-passage-regression") {
@@ -42,8 +42,10 @@ import type { M48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
import type { M48TRiskQualityResult } from "./m48tRiskQuality";
import type { M49TgsFailClosedResult } from "./m49TgsFailClosed";
import type { M49TgsFullShadowResult } from "./m49TgsFullShadow";
import type { VegetationShadowResult } from "./vegetationShadow";
export interface AdvancedLaboratoryResults {
vegetationShadow: VegetationShadowResult | null;
m47Graph: M47ReferenceGraphLabResult | null;
m48: M48AdvancedResult | null;
m48SmallStatic: M48SmallStaticRegressionResult | null;
@@ -967,6 +967,7 @@ export async function fetchAdvancedLaboratoryResults({
const e39 = settledCatalogValue(settled[7]);
const e40 = settledCatalogValue(settled[8]);
return {
vegetationShadow: null,
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
m48r3StaticOccupancy: null,
m48s: null, m48t: null, m49Tgs: null, m49TgsFull: null, m4Threat: null,
@@ -0,0 +1,278 @@
import type { LaboratoryFetch } from "./advancedResults";
const RESULT_ID = /^lab-v1-vegetation-shadow-[a-f0-9]{64}$/;
const SHA256 = /^[a-f0-9]{64}$/;
const CANDIDATES = ["ddrnet", "ppliteseg"] as const;
const ROUTE_MODES = ["source", "ddrnet", "ppliteseg", "urban", "rural", "offroad"] as const;
const VALIDATION_MODES = ["source", "truth", "ddrnet", "ppliteseg"] as const;
export type VegetationCandidateKey = typeof CANDIDATES[number];
export type VegetationRouteMode = typeof ROUTE_MODES[number];
export type VegetationValidationMode = typeof VALIDATION_MODES[number];
export interface VegetationCandidateMetrics {
candidate: VegetationCandidateKey;
loadedModelName: string;
checkpointSha256: string;
meanIouPercent: number;
publishedMeanIouPercent: number;
vegetationMeanIouPercent: number;
validationLatencyP95Ms: number;
validationThroughputFps: number;
shadowLatencyP95Ms: number;
shadowThroughputFps: number;
shadowPrewarmLatencyMs: number;
peakReservedVramBytes: number;
gpuName: string;
}
export interface VegetationVisualCase {
caseId: string;
sourceKind: "goose" | "ravnoves";
width: number;
height: number;
centerCropXyxy: readonly [number, number, number, number];
outsideCropState: "undefined" | "not-applicable";
assets: Readonly<Record<string, string>>;
}
export interface VegetationShadowResult {
resultId: string;
createdAtUtc: string;
status: "visual-shadow-ready-policy-not-authorized";
selectedCandidate: VegetationCandidateKey;
candidates: readonly VegetationCandidateMetrics[];
routeCases: readonly VegetationVisualCase[];
validationCases: readonly VegetationVisualCase[];
limitations: readonly string[];
visualShadowReady: true;
missionPolicyReadyForConfiguration: true;
authority: {
commandsEnabled: false;
navigationOrSafetyAccepted: false;
actuationAccepted: false;
cameraSemanticsCanClearRigidGeometry: false;
};
}
export class VegetationShadowContractError extends Error {}
function objectValue(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new VegetationShadowContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function arrayValue(value: unknown, label: string): readonly unknown[] {
if (!Array.isArray(value)) throw new VegetationShadowContractError(`${label}: ожидался массив.`);
return value;
}
function textValue(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new VegetationShadowContractError(`${label}: ожидалась строка.`);
}
return value;
}
function numberValue(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new VegetationShadowContractError(`${label}: ожидалось число.`);
}
return value;
}
function integerValue(value: unknown, label: string): number {
const parsed = numberValue(value, label);
if (!Number.isInteger(parsed) || parsed < 0) {
throw new VegetationShadowContractError(`${label}: ожидалось неотрицательное целое.`);
}
return parsed;
}
function exact(value: unknown, expected: unknown, label: string): void {
if (value !== expected) {
throw new VegetationShadowContractError(`${label}: контракт изменён.`);
}
}
function candidateKey(value: unknown, label: string): VegetationCandidateKey {
if (value !== "ddrnet" && value !== "ppliteseg") {
throw new VegetationShadowContractError(`${label}: неизвестная модель.`);
}
return value;
}
function candidateMetricsValue(
value: unknown,
candidate: VegetationCandidateKey,
): VegetationCandidateMetrics {
const row = objectValue(value, `vegetation.metrics.${candidate}`);
const validation = objectValue(row.validation_metrics, `${candidate}.validation_metrics`);
const validationTiming = objectValue(row.validation_timing, `${candidate}.validation_timing`);
const shadowTiming = objectValue(row.shadow_timing, `${candidate}.shadow_timing`);
const resource = objectValue(row.resource, `${candidate}.resource`);
const checkpointSha256 = textValue(row.checkpoint_sha256, `${candidate}.checkpoint_sha256`);
if (!SHA256.test(checkpointSha256)) {
throw new VegetationShadowContractError(`${candidate}.checkpoint_sha256: digest invalid.`);
}
return {
candidate,
loadedModelName: textValue(row.loaded_model_name, `${candidate}.loaded_model_name`),
checkpointSha256,
meanIouPercent: numberValue(validation.mean_iou_percent, `${candidate}.mean_iou_percent`),
publishedMeanIouPercent: numberValue(
validation.published_mean_iou_percent,
`${candidate}.published_mean_iou_percent`,
),
vegetationMeanIouPercent: numberValue(validation.vegetation_mean_iou, `${candidate}.vegetation_mean_iou`) * 100,
validationLatencyP95Ms: numberValue(validationTiming.latency_ms_p95, `${candidate}.validation_latency_p95`),
validationThroughputFps: numberValue(
validationTiming.throughput_fps_from_mean_inference,
`${candidate}.validation_throughput`,
),
shadowLatencyP95Ms: numberValue(shadowTiming.latency_ms_p95, `${candidate}.shadow_latency_p95`),
shadowThroughputFps: numberValue(
shadowTiming.throughput_fps_from_mean_inference,
`${candidate}.shadow_throughput`,
),
shadowPrewarmLatencyMs: numberValue(
shadowTiming.prewarm_latency_ms,
`${candidate}.shadow_prewarm_latency`,
),
peakReservedVramBytes: integerValue(resource.peak_reserved_vram_bytes, `${candidate}.vram`),
gpuName: textValue(resource.gpu_name, `${candidate}.gpu_name`),
};
}
function visualCaseValue(
value: unknown,
resultId: string,
expectedKind: "goose" | "ravnoves",
): VegetationVisualCase {
const row = objectValue(value, `vegetation.${expectedKind}.case`);
exact(row.source_kind, expectedKind, "vegetation.case.source_kind");
const caseId = textValue(row.case_id, "vegetation.case.case_id");
const crop = arrayValue(row.center_crop_xyxy, "vegetation.case.center_crop_xyxy")
.map((item, index) => integerValue(item, `vegetation.case.crop[${index}]`));
if (crop.length !== 4) {
throw new VegetationShadowContractError("vegetation.case.center_crop_xyxy: размер изменён.");
}
const assets = objectValue(row.assets, "vegetation.case.assets");
const projected: Record<string, string> = {};
for (const [key, raw] of Object.entries(assets)) {
const descriptor = objectValue(raw, `vegetation.case.assets.${key}`);
const path = textValue(descriptor.path, `vegetation.case.assets.${key}.path`);
const sha256 = textValue(descriptor.sha256, `vegetation.case.assets.${key}.sha256`);
if (!SHA256.test(sha256) || !path.startsWith(`visual/${expectedKind}/${caseId}/`)) {
throw new VegetationShadowContractError(`vegetation.case.assets.${key}: proof invalid.`);
}
projected[key] = `/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/assets/${path
.split("/")
.map(encodeURIComponent)
.join("/")}`;
}
const expectedAssets = expectedKind === "goose"
? VALIDATION_MODES
: ROUTE_MODES;
if (expectedAssets.some((key) => !projected[key])) {
throw new VegetationShadowContractError(`vegetation.case.assets: ${expectedKind} набор неполон.`);
}
const outsideCropState = row.outside_crop_state;
if (outsideCropState !== "undefined" && outsideCropState !== "not-applicable") {
throw new VegetationShadowContractError("vegetation.case.outside_crop_state: контракт изменён.");
}
return {
caseId,
sourceKind: expectedKind,
width: integerValue(row.width, "vegetation.case.width"),
height: integerValue(row.height, "vegetation.case.height"),
centerCropXyxy: crop as unknown as readonly [number, number, number, number],
outsideCropState,
assets: projected,
};
}
function parseResult(value: unknown, resultId: string): VegetationShadowResult {
const payload = objectValue(value, "Vegetation LAB");
exact(payload.schema_version, "missioncore.lab-v1-vegetation-shadow/v1", "vegetation.schema");
exact(payload.result_id, resultId, "vegetation.result_id");
exact(payload.status, "visual-shadow-ready-policy-not-authorized", "vegetation.status");
exact(payload.ground_truth, false, "vegetation.ground_truth");
exact(payload.access, "read-only", "vegetation.access");
const identity = objectValue(payload.identity, "vegetation.identity");
const metrics = objectValue(payload.metrics, "vegetation.metrics");
const candidates = objectValue(metrics.candidates, "vegetation.metrics.candidates");
const decision = objectValue(payload.decision, "vegetation.decision");
const authority = objectValue(payload.authority, "vegetation.authority");
const catalogs = objectValue(payload.catalogs, "vegetation.catalogs");
const selectedCandidate = candidateKey(identity.selected_candidate, "vegetation.selected_candidate");
exact(decision.selected_candidate, selectedCandidate, "vegetation.decision.selected_candidate");
exact(decision.visual_shadow_ready, true, "vegetation.decision.visual_shadow_ready");
exact(
decision.mission_policy_ready_for_configuration,
true,
"vegetation.decision.mission_policy_ready_for_configuration",
);
exact(decision.navigation_accepted, false, "vegetation.decision.navigation_accepted");
exact(decision.production_accepted, false, "vegetation.decision.production_accepted");
exact(authority.commands_enabled, false, "vegetation.authority.commands_enabled");
exact(
authority.navigation_or_safety_accepted,
false,
"vegetation.authority.navigation_or_safety_accepted",
);
exact(authority.actuation_accepted, false, "vegetation.authority.actuation_accepted");
exact(
authority.camera_semantics_can_clear_rigid_geometry,
false,
"vegetation.authority.camera_semantics_can_clear_rigid_geometry",
);
const routeCases = arrayValue(catalogs.ravnoves, "vegetation.catalogs.ravnoves")
.map((item) => visualCaseValue(item, resultId, "ravnoves"));
const validationCases = arrayValue(catalogs.goose, "vegetation.catalogs.goose")
.map((item) => visualCaseValue(item, resultId, "goose"));
if (routeCases.length !== 12 || validationCases.length !== 12) {
throw new VegetationShadowContractError("vegetation.catalogs: ожидалось 12 + 12 случаев.");
}
return {
resultId,
createdAtUtc: textValue(payload.created_at_utc, "vegetation.created_at_utc"),
status: "visual-shadow-ready-policy-not-authorized",
selectedCandidate,
candidates: CANDIDATES.map((candidate) => candidateMetricsValue(candidates[candidate], candidate)),
routeCases,
validationCases,
limitations: arrayValue(payload.limitations, "vegetation.limitations")
.map((item, index) => textValue(item, `vegetation.limitations[${index}]`)),
visualShadowReady: true,
missionPolicyReadyForConfiguration: true,
authority: {
commandsEnabled: false,
navigationOrSafetyAccepted: false,
actuationAccepted: false,
cameraSemanticsCanClearRigidGeometry: false,
},
};
}
export async function fetchVegetationShadowResult(
resultId: string,
{
fetcher = fetch,
signal,
}: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<VegetationShadowResult> {
if (!RESULT_ID.test(resultId)) {
throw new VegetationShadowContractError("Vegetation LAB identity недопустима.");
}
const response = await fetcher(
`/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new VegetationShadowContractError(`Vegetation LAB недоступна: HTTP ${response.status}.`);
}
return parseResult(await response.json(), resultId);
}
@@ -50,6 +50,7 @@ import { M48SFixedClassDetectorResultView } from "./M48SFixedClassDetectorResult
import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
import { M49TgsFailClosedResultView } from "./M49TgsFailClosedResult";
import { M49TgsFullShadowResultView } from "./M49TgsFullShadowResult";
import { VegetationShadowResultView } from "./VegetationShadowResult";
export { isAdvancedLaboratoryWorkId };
export type { AdvancedLaboratoryWorkId };
@@ -92,6 +93,9 @@ export function AdvancedLaboratoryResult({
failedSessionId: string | null;
replayError: string | null;
}) {
if (workId === "lab-v1-vegetation-shadow" && results.vegetationShadow) {
return <VegetationShadowResultView rigLabel={rigLabel} result={results.vegetationShadow} />;
}
if (workId === "m48-object-centric-quality" && results.m48) {
return <M48ObjectCentricQualityResultView rigLabel={rigLabel} result={results.m48} />;
}
@@ -0,0 +1,135 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { VegetationShadowResult } from "../../core/laboratory/vegetationShadow";
import {
VegetationRouteVisual,
VegetationValidationVisual,
} from "./VegetationShadowVisual";
function decimal(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
}
export function VegetationShadowResultView({
rigLabel,
result,
}: {
rigLabel: string;
result: VegetationShadowResult;
}) {
const selected = result.candidates.find(
(candidate) => candidate.candidate === result.selectedCandidate,
)!;
const alternative = result.candidates.find(
(candidate) => candidate.candidate !== result.selectedCandidate,
)!;
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="LAB V1 · растительность и mission-policy пресеты"
description="Две готовые fine-64 модели GOOSE проверены на полном validation split и перенесены в автономный визуальный shadow по RAVNOVES00. Интерфейс читает sealed-кадры локально и не зависит от доступности Worker 006."
status="Визуальный shadow готов · navigation authority OFF"
statusTone="warning"
facts={[
{ label: "Источник", value: `${rigLabel} RIGHT · 12 raw KB4 кадров + GOOSE validation 962` },
{ label: "Сравнение", value: "DDRNet-39 vs PPLiteSeg · official fine-64 weights" },
{ label: "Policy", value: "Urban / rural / off-road · mission-configurable" },
{ label: "Authority", value: "SHADOW ONLY · commands OFF · geometry stays authoritative" },
]}
brief={{
question: "Можно ли взять готовую сегментацию растительности, увидеть её на нашем маршруте и сразу проверить разные правила миссии?",
approach: "Обе модели последовательно прогнаны в одном изолированном CUDA-runtime: сначала 962 размеченных GOOSE-кадра, затем 12 детерминированных кадров RAVNOVES00. Для каждого кадра запечатаны source, обе семантики и три policy-проекции.",
principalResult: `${selected.loadedModelName} выбран по vegetation IoU ${decimal(selected.vegetationMeanIouPercent, 2)}% при shadow p95 ${decimal(selected.shadowLatencyP95Ms, 2)} ms. Визуальный результат доступен локально без Worker.`,
limitation: "RAVNOVES00 не размечен по fine-64, поэтому это перенос и визуальная проверка, а не доказательство точности или безопасности. Камерная семантика не может очищать жёсткую LiDAR/TGS occupancy.",
}}
method={{
completeness: "complete",
executionClass: "ai-inference",
pipelineId: "goose-fine64-ready-weights-to-ravnoves-policy-shadow/v1",
components: result.candidates.map((candidate) => ({
kind: "model" as const,
name: candidate.loadedModelName,
version: candidate.candidate,
role: candidate.candidate === result.selectedCandidate ? "selected policy provider" : "comparison candidate",
identitySha256: candidate.checkpointSha256,
})),
}}
/>
)}
evidence={(
<>
<LaboratoryEvidence
eyebrow="RAVNOVES00 · AUTONOMOUS VISUAL SHADOW"
title="Источник, обе модели и три правила миссии на одинаковых кадрах"
kind="diagnostic-model"
resizable
>
<VegetationRouteVisual result={result} />
</LaboratoryEvidence>
<LaboratoryEvidence
eyebrow="GOOSE · EXTERNAL VALIDATION EVIDENCE"
title="Независимая разметка: truth против DDRNet и PPLiteSeg"
kind="diagnostic-model"
resizable
>
<VegetationValidationVisual result={result} />
</LaboratoryEvidence>
</>
)}
result={(
<LaboratoryResultSummary
title="Готовые веса дают рабочую точку старта, но ещё не право ехать"
status={`${selected.loadedModelName} выбран для shadow`}
statusTone="warning"
metrics={[
{
label: "GOOSE mIoU",
value: `${decimal(selected.meanIouPercent, 2)}% / ${decimal(alternative.meanIouPercent, 2)}%`,
hint: `${selected.candidate} / ${alternative.candidate} · полный validation split`,
},
{
label: "Vegetation IoU",
value: `${decimal(selected.vegetationMeanIouPercent, 2)}% / ${decimal(alternative.vegetationMeanIouPercent, 2)}%`,
hint: "агрегация классов grass/vegetation/bush/tree и родственных fine-64 labels",
},
{
label: "RAVNOVES p95",
value: `${decimal(selected.shadowLatencyP95Ms, 2)} / ${decimal(alternative.shadowLatencyP95Ms, 2)} ms`,
hint: "чистый inference · одна тяжёлая модель за раз",
},
{
label: "Cold prewarm",
value: `${decimal(selected.shadowPrewarmLatencyMs, 1)} / ${decimal(alternative.shadowPrewarmLatencyMs, 1)} ms`,
hint: "один явный inference до допуска кадров; исключён из steady-state p95",
},
{
label: "RAVNOVES throughput",
value: `${decimal(selected.shadowThroughputFps, 1)} / ${decimal(alternative.shadowThroughputFps, 1)} FPS`,
hint: "изолированный Worker 006 · не realtime graph целиком",
},
{
label: "Peak VRAM",
value: `${decimal(selected.peakReservedVramBytes / 1024 ** 3, 2)} / ${decimal(alternative.peakReservedVramBytes / 1024 ** 3, 2)} GiB`,
hint: `${selected.candidate} / ${alternative.candidate} · RTX 4090`,
},
{
label: "Autonomous evidence",
value: "12 route + 12 validation",
hint: "только текущий кадр загружается в viewer; Worker не требуется",
},
]}
conclusion={{
proved: "Обе официальные fine-64 модели запускаются на Worker 006, проходят полный GOOSE validation и дают воспроизводимые растительные маски на 12 фиксированных RAVNOVES00 кадрах. Urban/rural/off-road policy-проекции формируются без повторного inference.",
notProved: "Не доказаны accuracy на нашем fisheye-домене, различение тонкой травы от толстого ствола во всех условиях, temporal stability, collision safety и physical-live поведение ровера.",
decision: "Сохранить выбранную модель как shadow provider. Следующий критический блок — разметить небольшой hard-case island нашего офф-роуда: трава, папоротник, куст с толстыми стволами и дерево; затем калибровать policy без ослабления LiDAR/TGS fail-closed геометрии.",
}}
/>
)}
/>
);
}
@@ -0,0 +1,157 @@
import { useState } from "react";
import { Icon, IconButton, StatusBadge } from "@nodedc/ui-react";
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
import type {
VegetationRouteMode,
VegetationShadowResult,
VegetationValidationMode,
VegetationVisualCase,
} from "../../core/laboratory/vegetationShadow";
const ROUTE_MODES = [
{ value: "source", label: "SOURCE" },
{ value: "ddrnet", label: "DDRNET" },
{ value: "ppliteseg", label: "PPLITE" },
{ value: "urban", label: "URBAN" },
{ value: "rural", label: "RURAL" },
{ value: "offroad", label: "OFF-ROAD" },
] as const;
const VALIDATION_MODES = [
{ value: "source", label: "SOURCE" },
{ value: "truth", label: "TRUTH" },
{ value: "ddrnet", label: "DDRNET" },
{ value: "ppliteseg", label: "PPLITE" },
] as const;
function VegetationScene({
item,
mode,
}: {
item: VegetationVisualCase;
mode: string;
}) {
const overlay = mode === "source" ? null : item.assets[mode];
return (
<div className="recorded-evidence-image-scene">
<img src={item.assets.source} alt="" draggable={false} />
{overlay ? <img src={overlay} alt="" draggable={false} /> : null}
</div>
);
}
export function VegetationRouteVisual({ result }: { result: VegetationShadowResult }) {
const [index, setIndex] = useState(0);
const [mode, setMode] = useState<VegetationRouteMode>("offroad");
const [expanded, setExpanded] = useState(false);
const item = result.routeCases[index] ?? null;
const selected = result.candidates.find(
(candidate) => candidate.candidate === result.selectedCandidate,
);
return (
<LaboratoryEvidenceViewer
label="RAVNOVES00 vegetation policy shadow"
className="m48-atlas-visual"
mode={mode}
modes={ROUTE_MODES}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
actions={(
<>
<IconButton
label="Предыдущий vegetation shadow кадр"
disabled={!result.routeCases.length}
onClick={() => setIndex((current) => (
current - 1 + result.routeCases.length
) % result.routeCases.length)}
>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton
label="Следующий vegetation shadow кадр"
disabled={!result.routeCases.length}
onClick={() => setIndex((current) => (current + 1) % result.routeCases.length)}
>
<Icon name="chevron-right" size={16} />
</IconButton>
</>
)}
overlay={item ? (
<div className="m48-atlas-visual__case">
<StatusBadge tone="warning">SHADOW ONLY</StatusBadge>
<strong>RAVNOVES00 · {item.caseId} · {mode.toUpperCase()}</strong>
<small>
{selected?.loadedModelName ?? result.selectedCandidate} policy provider
{" · "}center crop 600×600
{" · "}outside crop UNKNOWN
</small>
</div>
) : null}
>
{item ? (
<VegetationScene item={item} mode={mode} />
) : (
<div className="m48-atlas-visual__state" role="alert">
<Icon name="alert" size={18} />
RAVNOVES vegetation shadow каталог пуст.
</div>
)}
</LaboratoryEvidenceViewer>
);
}
export function VegetationValidationVisual({ result }: { result: VegetationShadowResult }) {
const [index, setIndex] = useState(0);
const [mode, setMode] = useState<VegetationValidationMode>("truth");
const [expanded, setExpanded] = useState(false);
const item = result.validationCases[index] ?? null;
return (
<LaboratoryEvidenceViewer
label="GOOSE validation vegetation comparison"
className="m48-atlas-visual"
mode={mode}
modes={VALIDATION_MODES}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
actions={(
<>
<IconButton
label="Предыдущий GOOSE validation кадр"
disabled={!result.validationCases.length}
onClick={() => setIndex((current) => (
current - 1 + result.validationCases.length
) % result.validationCases.length)}
>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton
label="Следующий GOOSE validation кадр"
disabled={!result.validationCases.length}
onClick={() => setIndex((current) => (current + 1) % result.validationCases.length)}
>
<Icon name="chevron-right" size={16} />
</IconButton>
</>
)}
overlay={item ? (
<div className="m48-atlas-visual__case">
<StatusBadge tone="accent">GOOSE VALIDATION</StatusBadge>
<strong>{item.caseId} · {mode.toUpperCase()}</strong>
<small>official fine-64 labels · fixed 512×512 preprocessing · visual sample</small>
</div>
) : null}
>
{item ? (
<VegetationScene item={item} mode={mode} />
) : (
<div className="m48-atlas-visual__state" role="alert">
<Icon name="alert" size={18} />
GOOSE validation каталог пуст.
</div>
)}
</LaboratoryEvidenceViewer>
);
}
@@ -63,6 +63,13 @@ interface KnownWorkDefinition {
const rig = (rigLabel: string): string => rigLabel.trim() || "Сенсорный риг";
const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`>, KnownWorkDefinition>> = {
"lab-v1-vegetation-shadow": {
profileId: "rig-ravnoves-perception-gate-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · RAVNOVES00 vegetation shadow`,
experimentId: "lab-v1-vegetation-mission-policy",
experimentName: "GOOSE ready weights → RAVNOVES vegetation policy",
variantName: "LAB V1 · DDRNet vs PPLiteSeg · urban/rural/off-road presets",
},
"m48-object-centric-quality": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Camera + prediction-free spatial evidence`,
@@ -18,6 +18,7 @@ function mergeResults(
next: AdvancedLaboratoryResults,
): AdvancedLaboratoryResults {
return {
vegetationShadow: next.vegetationShadow ?? current.vegetationShadow,
m47Graph: next.m47Graph ?? current.m47Graph,
m48: next.m48 ?? current.m48,
m48SmallStatic: next.m48SmallStatic ?? current.m48SmallStatic,
@@ -121,6 +122,7 @@ export function useAdvancedLaboratoryCatalog({
const indexedResultId = index.find((item) => item.workId === selectedWorkId)?.resultId;
if (
[
"lab-v1-vegetation-shadow",
"m47-reference-graph-shadow",
"m48-object-centric-quality",
"m48-small-static-passage-regression",