feat(lab): publish full TGS shadow evidence

This commit is contained in:
DCCONSTRUCTIONS
2026-08-26 23:07:06 +03:00
parent 40c850b167
commit d722b0f82b
22 changed files with 1508 additions and 28 deletions
@@ -47,6 +47,7 @@ import { fetchM48R3StaticOccupancyShadow } from "./m48r3StaticOccupancyShadow";
import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
import { fetchM49TgsFailClosedResult } from "./m49TgsFailClosed";
import { fetchM49TgsFullShadowResult } from "./m49TgsFullShadow";
export type AdvancedLaboratoryWorkId =
| "m48-object-centric-quality"
@@ -56,6 +57,7 @@ export type AdvancedLaboratoryWorkId =
| "m48s-fixed-class-detector"
| "m48t-risk-quality-temporal"
| "m49-tgs-fail-closed-evidence"
| "m49-tgs-full-shadow"
| "m47-reference-graph-shadow"
| "m4-replay-threat"
| "l3-pointpillars-visual-audit"
@@ -105,6 +107,7 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
"m48s-fixed-class-detector",
"m48t-risk-quality-temporal",
"m49-tgs-fail-closed-evidence",
"m49-tgs-full-shadow",
"m47-reference-graph-shadow",
"m4-replay-threat",
"l3-pointpillars-visual-audit",
@@ -149,6 +152,7 @@ const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
"m48s-fixed-class-detector": "m48s-fixed-class-detector-lab",
"m48t-risk-quality-temporal": "(?:m48t-risk-quality-temporal-lab|m48q-native-risk-quality-lab)",
"m49-tgs-fail-closed-evidence": "m49-tgs-fail-closed",
"m49-tgs-full-shadow": "m49-tgs-full-shadow",
"m47-reference-graph-shadow": "m47-reference-graph-lab",
"m4-replay-threat": "m4-threat-replay",
"l3-pointpillars-visual-audit": "l3-pointpillars-visual-audit",
@@ -201,6 +205,7 @@ export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
m48s: null,
m48t: null,
m49Tgs: null,
m49TgsFull: null,
m4Threat: null,
l3: null,
l31: null,
@@ -332,6 +337,7 @@ export function advancedLaboratoryResultAvailable(
: workId === "m48s-fixed-class-detector" ? results.m48s !== null
: workId === "m48t-risk-quality-temporal" ? results.m48t !== null
: workId === "m49-tgs-fail-closed-evidence" ? results.m49Tgs !== null
: workId === "m49-tgs-full-shadow" ? results.m49TgsFull !== null
: workId === "m47-reference-graph-shadow" ? results.m47Graph !== null
: workId === "m4-replay-threat" ? results.m4Threat !== null
: workId === "l3-pointpillars-visual-audit" ? results.l3 !== null
@@ -402,6 +408,9 @@ export async function fetchAdvancedLaboratoryResult(
} else if (workId === "m49-tgs-fail-closed-evidence") {
if (!resultId) throw new AdvancedLaboratoryContractError("M4.9 TGS LAB identity не выбрана.");
results.m49Tgs = await fetchM49TgsFailClosedResult(resultId, { fetcher, signal });
} else if (workId === "m49-tgs-full-shadow") {
if (!resultId) throw new AdvancedLaboratoryContractError("M4.9 full TGS shadow identity не выбрана.");
results.m49TgsFull = await fetchM49TgsFullShadowResult(resultId, { fetcher, signal });
} else if (workId === "m47-reference-graph-shadow") {
if (!resultId) {
throw new AdvancedLaboratoryContractError("M4.7 LAB identity не выбрана.");
@@ -41,6 +41,7 @@ import type { M48R3StaticOccupancyShadowResult } from "./m48r3StaticOccupancySha
import type { M48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
import type { M48TRiskQualityResult } from "./m48tRiskQuality";
import type { M49TgsFailClosedResult } from "./m49TgsFailClosed";
import type { M49TgsFullShadowResult } from "./m49TgsFullShadow";
export interface AdvancedLaboratoryResults {
m47Graph: M47ReferenceGraphLabResult | null;
@@ -51,6 +52,7 @@ export interface AdvancedLaboratoryResults {
m48s: M48SFixedClassDetectorResult | null;
m48t: M48TRiskQualityResult | null;
m49Tgs: M49TgsFailClosedResult | null;
m49TgsFull: M49TgsFullShadowResult | null;
m4Threat: M4ThreatReplayResult | null;
l3: L3PointPillarsVisualAuditResult | null;
l31: L31PointPillarsRavnovesResult | null;
@@ -969,7 +969,7 @@ export async function fetchAdvancedLaboratoryResults({
return {
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
m48r3StaticOccupancy: null,
m48s: null, m48t: null, m49Tgs: null, m4Threat: null,
m48s: null, m48t: null, m49Tgs: null, m49TgsFull: null, m4Threat: null,
l3: null, l31: null, l32: null, l33: null,
e31,
e32,
@@ -0,0 +1,327 @@
import type { LaboratoryFetch } from "./advancedResults";
const RESULT_ID = /^m49-tgs-full-shadow-[a-f0-9]{64}$/;
export type M49TgsFullShadowStateCode = 0 | 1 | 2 | 3;
export interface M49TgsFullShadowResult {
resultId: string;
createdAtUtc: string;
source: {
sourcePackSha256: string;
linkedVisualResultId: string;
};
configuration: {
configSha256: string;
cellSizeM: number;
radiusM: number;
historySeconds: number;
};
execution: {
wrapperElapsedSeconds: number;
};
timeline: {
frameCount: 4489;
availableLidarFrameCount: 3928;
missingLidarFrameCount: 561;
durationSeconds: number;
effectiveFps: number;
};
pointAccounting: {
eligible: number;
ground: number;
nonground: number;
rejected: number;
unaccounted: 0;
};
performance: {
candidateTgsMs: { p50: number; p95: number; p99: number; max: number };
completionAgeMs: { p50: number; p95: number; p99: number; max: number };
capacityDropCount: number;
};
acceptance: {
representationComplete: true;
visualQualityAccepted: false;
integratedGraphPerformanceAccepted: false;
allFramesAccounted: boolean;
allEligiblePointsAccounted: boolean;
};
decision: {
state: string;
candidateRetained: boolean;
nextAction: string;
};
}
export interface M49TgsFullShadowSpatial {
resultId: string;
sourceSequence: number;
sourceFrameIndex: number;
sessionSeconds: number;
sampleAvailable: boolean;
costmap: {
cellSizeM: number;
radiusM: number;
centersXyM: readonly (readonly [number, number])[];
states: readonly M49TgsFullShadowStateCode[];
zBoundsM: readonly (readonly [number | null, number | null])[];
};
metrics: {
eligiblePointCount: number;
groundPointCount: number;
nongroundPointCount: number;
rejectedPointCount: number;
occupiedCellCount: number;
};
}
export interface M49TgsFullShadowSpatialChunk {
resultId: string;
start: number;
count: number;
frames: readonly M49TgsFullShadowSpatial[];
}
export class M49TgsFullShadowContractError extends Error {}
function objectValue(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new M49TgsFullShadowContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function text(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new M49TgsFullShadowContractError(`${label}: ожидалась строка.`);
}
return value;
}
function numberValue(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new M49TgsFullShadowContractError(`${label}: ожидалось число.`);
}
return value;
}
function integer(value: unknown, label: string): number {
const parsed = numberValue(value, label);
if (!Number.isSafeInteger(parsed) || parsed < 0) {
throw new M49TgsFullShadowContractError(`${label}: ожидалось целое число.`);
}
return parsed;
}
function booleanValue(value: unknown, label: string): boolean {
if (typeof value !== "boolean") {
throw new M49TgsFullShadowContractError(`${label}: ожидался boolean.`);
}
return value;
}
function exact<T extends string | boolean | number>(value: unknown, expected: T, label: string): T {
if (value !== expected) throw new M49TgsFullShadowContractError(`${label}: нарушен контракт.`);
return expected;
}
function timing(value: unknown, label: string) {
const row = objectValue(value, label);
return {
p50: numberValue(row.p50, `${label}.p50`),
p95: numberValue(row.p95, `${label}.p95`),
p99: numberValue(row.p99, `${label}.p99`),
max: numberValue(row.max, `${label}.max`),
};
}
export async function fetchM49TgsFullShadowResult(
id: string,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M49TgsFullShadowResult> {
if (!RESULT_ID.test(id)) throw new M49TgsFullShadowContractError("M49 full-shadow identity недопустима.");
const response = await fetcher(
`/api/v1/laboratory/m49/tgs-full-shadow/${encodeURIComponent(id)}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M49TgsFullShadowContractError(`M49 full shadow недоступен: HTTP ${response.status}.`);
const payload = objectValue(await response.json(), "M49 full shadow");
exact(payload.schema_version, "missioncore.m49-tgs-full-shadow-view/v1", "M49 full schema");
exact(payload.result_id, id, "M49 full result");
exact(payload.ground_truth, false, "M49 full ground truth");
exact(payload.access, "read-only", "M49 full access");
const source = objectValue(payload.source, "M49 full source");
const configuration = objectValue(payload.configuration, "M49 full configuration");
const execution = objectValue(payload.execution, "M49 full execution");
const timeline = objectValue(payload.timeline, "M49 full timeline");
const accounting = objectValue(payload.point_accounting, "M49 full accounting");
const performance = objectValue(payload.performance, "M49 full performance");
const acceptance = objectValue(payload.acceptance, "M49 full acceptance");
const decision = objectValue(payload.decision, "M49 full decision");
return {
resultId: text(payload.result_id, "M49 full result"),
createdAtUtc: text(payload.created_at_utc, "M49 full created"),
source: {
sourcePackSha256: text(source.source_pack_sha256, "M49 full source pack"),
linkedVisualResultId: text(source.linked_visual_result_id, "M49 full visual result"),
},
configuration: {
configSha256: text(configuration.config_sha256, "M49 full config"),
cellSizeM: numberValue(configuration.cell_size_m, "M49 full cell size"),
radiusM: numberValue(configuration.radius_m, "M49 full radius"),
historySeconds: numberValue(configuration.history_seconds, "M49 full history"),
},
execution: { wrapperElapsedSeconds: numberValue(execution.wrapper_elapsed_seconds, "M49 full wall") },
timeline: {
frameCount: exact(integer(timeline.frame_count, "M49 full frames"), 4489, "M49 full frames"),
availableLidarFrameCount: exact(integer(timeline.available_lidar_frame_count, "M49 full available"), 3928, "M49 full available"),
missingLidarFrameCount: exact(integer(timeline.missing_lidar_frame_count, "M49 full missing"), 561, "M49 full missing"),
durationSeconds: numberValue(timeline.duration_seconds, "M49 full duration"),
effectiveFps: numberValue(timeline.effective_fps, "M49 full fps"),
},
pointAccounting: {
eligible: integer(accounting.eligible, "M49 full eligible"),
ground: integer(accounting.ground, "M49 full ground"),
nonground: integer(accounting.nonground, "M49 full nonground"),
rejected: integer(accounting.rejected, "M49 full rejected"),
unaccounted: exact(integer(accounting.unaccounted, "M49 full unaccounted"), 0, "M49 full unaccounted"),
},
performance: {
candidateTgsMs: timing(performance.candidate_tgs_ms, "M49 full TGS"),
completionAgeMs: timing(performance.completion_age_ms, "M49 full completion"),
capacityDropCount: integer(performance.capacity_drop_count, "M49 full drops"),
},
acceptance: {
representationComplete: exact(acceptance.representation_complete, true, "M49 full representation"),
visualQualityAccepted: exact(acceptance.visual_quality_accepted, false, "M49 full visual"),
integratedGraphPerformanceAccepted: exact(acceptance.integrated_graph_performance_accepted, false, "M49 full integrated"),
allFramesAccounted: booleanValue(acceptance.all_frames_accounted, "M49 full frame accounting"),
allEligiblePointsAccounted: booleanValue(acceptance.all_eligible_points_accounted, "M49 full point accounting"),
},
decision: {
state: text(decision.state, "M49 full decision"),
candidateRetained: booleanValue(decision.candidate_retained, "M49 full retained"),
nextAction: text(decision.next_action, "M49 full next action"),
},
};
}
function pair(value: unknown, label: string): readonly [number, number] {
if (!Array.isArray(value) || value.length !== 2) {
throw new M49TgsFullShadowContractError(`${label}: неверная размерность.`);
}
return [numberValue(value[0], label), numberValue(value[1], label)];
}
function parseSpatial(
id: string,
sourceSequence: number,
payload: Record<string, unknown>,
sharedCostmap?: Record<string, unknown>,
): M49TgsFullShadowSpatial {
exact(payload.source_sequence, sourceSequence, "M49 full spatial sequence");
const costmap = sharedCostmap ?? objectValue(payload.costmap, "M49 full costmap");
const metrics = objectValue(payload.metrics, "M49 full frame metrics");
const centers = costmap.centers_xy_m;
const statesRaw = sharedCostmap ? payload.states : costmap.states;
const zBoundsRaw = sharedCostmap ? payload.z_bounds_m : costmap.z_bounds_m;
if (!Array.isArray(centers) || !Array.isArray(statesRaw) || !Array.isArray(zBoundsRaw)
|| centers.length !== 2244 || statesRaw.length !== 2244 || zBoundsRaw.length !== 2244) {
throw new M49TgsFullShadowContractError("M49 full costmap: неверная размерность.");
}
const states = statesRaw.map((value, index) => {
const parsed = integer(value, `M49 full state ${index}`);
if (parsed > 3) throw new M49TgsFullShadowContractError("M49 full state недопустим.");
return parsed as M49TgsFullShadowStateCode;
});
const zBounds = zBoundsRaw.map((value, index) => {
if (!Array.isArray(value) || value.length !== 2) {
throw new M49TgsFullShadowContractError(`M49 full z ${index}: неверная размерность.`);
}
return value.map((item) => item === null
? null
: numberValue(item, `M49 full z ${index}`)) as [number | null, number | null];
});
const sampleAvailable = booleanValue(payload.sample_available, "M49 full sample available");
if (!sampleAvailable && states.some((state) => state !== 0)) {
throw new M49TgsFullShadowContractError("M49 missing LiDAR frame не остался UNOBSERVED.");
}
return {
resultId: id,
sourceSequence,
sourceFrameIndex: integer(payload.source_frame_index, "M49 full source frame"),
sessionSeconds: numberValue(payload.session_seconds, "M49 full session seconds"),
sampleAvailable,
costmap: {
cellSizeM: numberValue(costmap.cell_size_m, "M49 full cell size"),
radiusM: numberValue(costmap.radius_m, "M49 full radius"),
centersXyM: centers.map((value, index) => pair(value, `M49 full center ${index}`)),
states,
zBoundsM: zBounds,
},
metrics: {
eligiblePointCount: integer(metrics.eligible_point_count, "M49 full eligible"),
groundPointCount: integer(metrics.ground_point_count, "M49 full ground"),
nongroundPointCount: integer(metrics.nonground_point_count, "M49 full nonground"),
rejectedPointCount: integer(metrics.rejected_point_count, "M49 full rejected"),
occupiedCellCount: integer(metrics.occupied_cell_count, "M49 full occupied cells"),
},
};
}
export async function fetchM49TgsFullShadowSpatial(
id: string,
sourceSequence: number,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M49TgsFullShadowSpatial> {
if (!RESULT_ID.test(id) || !Number.isSafeInteger(sourceSequence) || sourceSequence < 0 || sourceSequence >= 4489) {
throw new M49TgsFullShadowContractError("M49 full-shadow frame identity недопустима.");
}
const response = await fetcher(
`/api/v1/laboratory/m49/tgs-full-shadow/${encodeURIComponent(id)}/frames/${sourceSequence}/spatial`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M49TgsFullShadowContractError(`M49 full-shadow frame недоступен: HTTP ${response.status}.`);
const payload = objectValue(await response.json(), "M49 full spatial");
exact(payload.schema_version, "missioncore.m49-tgs-full-shadow-spatial/v1", "M49 full spatial schema");
exact(payload.result_id, id, "M49 full spatial result");
return parseSpatial(id, sourceSequence, payload);
}
export async function fetchM49TgsFullShadowSpatialChunk(
id: string,
start: number,
count = 24,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M49TgsFullShadowSpatialChunk> {
if (!RESULT_ID.test(id) || !Number.isSafeInteger(start) || start < 0 || start >= 4489
|| !Number.isSafeInteger(count) || count < 1 || count > 24) {
throw new M49TgsFullShadowContractError("M49 full-shadow chunk identity недопустима.");
}
const response = await fetcher(
`/api/v1/laboratory/m49/tgs-full-shadow/${encodeURIComponent(id)}/spatial/chunk?start=${start}&count=${count}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) throw new M49TgsFullShadowContractError(`M49 full-shadow chunk недоступен: HTTP ${response.status}.`);
const payload = objectValue(await response.json(), "M49 full spatial chunk");
exact(payload.schema_version, "missioncore.m49-tgs-full-shadow-spatial-chunk/v1", "M49 full chunk schema");
exact(payload.result_id, id, "M49 full chunk result");
exact(payload.start, start, "M49 full chunk start");
const returnedCount = integer(payload.count, "M49 full chunk count");
if (!Array.isArray(payload.frames) || payload.frames.length !== returnedCount
|| returnedCount !== Math.min(count, 4489 - start)) {
throw new M49TgsFullShadowContractError("M49 full chunk: неверная размерность.");
}
const costmap = objectValue(payload.costmap, "M49 full chunk costmap");
return {
resultId: id,
start,
count: returnedCount,
frames: payload.frames.map((value, index) => parseSpatial(
id,
start + index,
objectValue(value, `M49 full chunk frame ${index}`),
costmap,
)),
};
}
@@ -49,6 +49,7 @@ import { M48R3StaticOccupancyShadowResultView } from "./M48R3StaticOccupancyShad
import { M48SFixedClassDetectorResultView } from "./M48SFixedClassDetectorResult";
import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
import { M49TgsFailClosedResultView } from "./M49TgsFailClosedResult";
import { M49TgsFullShadowResultView } from "./M49TgsFullShadowResult";
export { isAdvancedLaboratoryWorkId };
export type { AdvancedLaboratoryWorkId };
@@ -112,6 +113,9 @@ export function AdvancedLaboratoryResult({
if (workId === "m49-tgs-fail-closed-evidence" && results.m49Tgs) {
return <M49TgsFailClosedResultView rigLabel={rigLabel} result={results.m49Tgs} />;
}
if (workId === "m49-tgs-full-shadow" && results.m49TgsFull) {
return <M49TgsFullShadowResultView rigLabel={rigLabel} result={results.m49TgsFull} />;
}
if (workId === "m47-reference-graph-shadow" && results.m47Graph) {
return <M47ReferenceGraphResultView rigLabel={rigLabel} result={results.m47Graph} />;
}
@@ -0,0 +1,164 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type {
RecordedEvidenceSemanticClass,
RecordedEvidenceSemanticPaletteEntry,
} from "../../components/laboratory/RecordedEvidenceSemanticMaskOverlay";
import {
fetchM49TgsFullShadowSpatialChunk,
type M49TgsFullShadowResult,
type M49TgsFullShadowSpatial,
type M49TgsFullShadowSpatialChunk,
type M49TgsFullShadowStateCode,
} from "../../core/laboratory/m49TgsFullShadow";
import {
M4ReplayThreatVisual,
type M4ReplayClassifiedSpatialFrame,
} from "./M4ReplayThreatVisual";
const CLASSES: readonly RecordedEvidenceSemanticClass[] = [
{ id: 1, label: "Ground support" },
{ id: 2, label: "Non-ground occupied" },
{ id: 3, label: "Unknown / rejected" },
];
const PALETTE: readonly RecordedEvidenceSemanticPaletteEntry[] = [
{ classId: 1, color: { kind: "token", token: "--nodedc-success-rgb" } },
{ classId: 2, color: { kind: "token", token: "--nodedc-danger-rgb" } },
{ classId: 3, color: { kind: "token", token: "--nodedc-warning-rgb" } },
];
const CHUNK_FRAMES = 24;
const RETAINED_CHUNKS = 4;
function cellState(code: M49TgsFullShadowStateCode): M4ReplayClassifiedSpatialFrame["cellsMapGravityLocal"][number]["state"] {
if (code === 1) return "ground-support";
if (code === 2) return "nonground-occupied";
if (code === 3) return "unknown-rejected";
return "unobserved";
}
function message(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "Полный TGS spatial frame недоступен.";
}
export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowResult }) {
const [activeSequence, setActiveSequence] = useState<number | null>(null);
const [chunks, setChunks] = useState<ReadonlyMap<number, M49TgsFullShadowSpatialChunk>>(
() => new Map(),
);
const [error, setError] = useState<string | null>(null);
const inFlightRef = useRef(new Map<number, AbortController>());
const activeChunkStart = activeSequence === null
? null
: Math.floor(activeSequence / CHUNK_FRAMES) * CHUNK_FRAMES;
const activeChunkStartRef = useRef(activeChunkStart);
activeChunkStartRef.current = activeChunkStart;
useEffect(() => {
for (const controller of inFlightRef.current.values()) controller.abort();
inFlightRef.current.clear();
setChunks(new Map());
setError(null);
return () => {
for (const controller of inFlightRef.current.values()) controller.abort();
inFlightRef.current.clear();
};
}, [result.resultId]);
useEffect(() => {
if (activeChunkStart === null) return;
const desiredStarts = [activeChunkStart, activeChunkStart + CHUNK_FRAMES]
.filter((start) => start < result.timeline.frameCount);
const desired = new Set(desiredStarts);
for (const [start, controller] of inFlightRef.current) {
if (desired.has(start)) continue;
controller.abort();
inFlightRef.current.delete(start);
}
for (const start of desiredStarts) {
if (chunks.has(start) || inFlightRef.current.has(start)) continue;
const controller = new AbortController();
inFlightRef.current.set(start, controller);
void fetchM49TgsFullShadowSpatialChunk(result.resultId, start, CHUNK_FRAMES, {
signal: controller.signal,
})
.then((chunk) => {
if (controller.signal.aborted) return;
setChunks((current) => {
const next = new Map(current);
next.set(start, chunk);
const center = activeChunkStartRef.current ?? start;
const retained = [...next.keys()]
.sort((left, right) => Math.abs(left - center) - Math.abs(right - center))
.slice(0, RETAINED_CHUNKS);
return new Map(retained.map((key) => [key, next.get(key)!]));
});
if (start === activeChunkStartRef.current) setError(null);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted && start === activeChunkStartRef.current) {
setError(message(caught));
}
})
.finally(() => {
if (inFlightRef.current.get(start) === controller) inFlightRef.current.delete(start);
});
break;
}
}, [activeChunkStart, chunks, result.resultId, result.timeline.frameCount]);
const spatial = useMemo<M49TgsFullShadowSpatial | null>(() => {
if (activeSequence === null || activeChunkStart === null) return null;
return chunks.get(activeChunkStart)?.frames.find(
(frame) => frame.sourceSequence === activeSequence,
) ?? null;
}, [activeChunkStart, activeSequence, chunks]);
const loading = activeSequence !== null && !spatial && !error;
const classifiedFrame = useMemo<M4ReplayClassifiedSpatialFrame | null>(() => {
if (!spatial) return null;
return {
sourceSequence: spatial.sourceSequence,
sampleAvailable: spatial.sampleAvailable,
sourcePointCount: spatial.metrics.eligiblePointCount,
pointsMapGravityLocalXyzM: [],
pointClassIds: [],
cellsMapGravityLocal: spatial.costmap.centersXyM.map((center, index) => ({
centerXyM: center,
zBoundsM: spatial.costmap.zBoundsM[index]!,
state: cellState(spatial.costmap.states[index]!),
})),
cellSizeM: spatial.costmap.cellSizeM,
classes: CLASSES,
palette: PALETTE,
};
}, [spatial]);
const handleSequenceChange = useCallback((sequence: number | null) => {
setActiveSequence(sequence);
}, []);
return (
<M4ReplayThreatVisual
resultId={result.source.linkedVisualResultId}
showReviewAnchorBoxes={false}
reviewLabel="4 489 source-paced TGS frames"
evidenceLabel="M49 · full TGS shadow"
initialSpatialMode="3d"
onActiveSequenceChange={handleSequenceChange}
classifiedSpatialLayer={{
label: "TGS full shadow · causal rolling 1 s",
pointLayerLabel: "SOURCE POINTS",
cellLayerLabel: "TGS COSTMAP",
expectedAtSequence: true,
frame: classifiedFrame,
loading,
error,
replacePointCloud: false,
}}
/>
);
}
@@ -0,0 +1,90 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { M49TgsFullShadowResult } from "../../core/laboratory/m49TgsFullShadow";
import { M49TgsFullShadowEvidence } from "./M49TgsFullShadowEvidence";
function number(value: number, digits = 1): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
}
export function M49TgsFullShadowResultView({
rigLabel,
result,
}: {
rigLabel: string;
result: M49TgsFullShadowResult;
}) {
const accepted = result.decision.candidateRetained;
const status = accepted
? "Source-paced CPU shadow принят; визуальное и integrated-graph качество ещё проверяются"
: "Source-paced CPU shadow не прошёл performance gate";
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="M4.9T5 · полный source-paced TRAVEL TGS shadow"
description="Один CPU-only процесс прошёл весь recorded timeline RAVNOVES00 в исходном темпе. Камера остаётся владельцем времени; dense source cloud сохраняется, поверх него показывается четырёхсостояний TGS costmap."
status={status}
statusTone={accepted ? "success" : "danger"}
facts={[
{ label: "Конфигурация", value: `${rigLabel} RIGHT · gravity-aligned LiDAR · causal ${number(result.configuration.historySeconds)} с` },
{ label: "Timeline", value: `${result.timeline.frameCount.toLocaleString("ru-RU")} кадров · ${result.timeline.availableLidarFrameCount.toLocaleString("ru-RU")} LiDAR · ${result.timeline.missingLidarFrameCount} UNOBSERVED` },
{ label: "Нагрузка", value: `Worker 006 CPU-only · GPU 0 · ${number(result.timeline.effectiveFps, 3)} source FPS` },
{ label: "Authority", value: "REPLAY-SIMULATED · navigation/actuation OFF · integrated graph отдельно" },
]}
brief={{
question: "Удерживает ли готовый TRAVEL TGS полный десятигерцовый replay без очереди и потери кадров?",
approach: "Все 4 489 camera frames планируются по исходным timestamps. Для 3 928 доступных LiDAR frames выполняется causal rolling 1 s; 561 пропуск остаётся полностью UNOBSERVED.",
principalResult: `${result.timeline.frameCount}/4 489 frames и ${result.pointAccounting.eligible.toLocaleString("ru-RU")} eligible points учтены; TGS p95/p99 ${number(result.performance.candidateTgsMs.p95, 2)}/${number(result.performance.candidateTgsMs.p99, 2)} мс, completion age p99 ${number(result.performance.completionAgeMs.p99, 2)} мс, capacity drops ${result.performance.capacityDropCount}.`,
limitation: "Это isolated CPU shadow. Он ещё не доказывает качество красных occupied-ячеек, проходимость для конкретного корпуса или регрессию FPS полного world-state graph.",
}}
method={{
completeness: "complete",
executionClass: "deterministic",
pipelineId: "travel-tgs-full-source-paced-shadow/v1",
components: [
{ kind: "source", name: "RAVNOVES00", version: "4 489-frame recorded timeline", role: "camera-owned source clock + registered LiDAR", identitySha256: result.source.sourcePackSha256 },
{ kind: "algorithm", name: "TRAVEL GroundSeg", version: "95dc2fbd66a343efd9060c45a5711b6307a950a4", role: "gravity-aligned ground/non-ground separation; AOS OFF", identitySha256: result.configuration.configSha256 },
{ kind: "algorithm", name: "fail-closed costmap adapter", version: "v1", role: "occupied > rejected > ground > unobserved; no free inference", identitySha256: result.resultId.split("-").at(-1) ?? null },
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.9T5 VISUAL EVIDENCE · FULL CAMERA TIMELINE + SOURCE CLOUD + TGS COSTMAP"
title="Полный timeline: dense исходные точки сохранены, TGS-ячейки синхронны каждому кадру"
kind="recorded-replay"
resizable
>
<M49TgsFullShadowEvidence result={result} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Что доказал полный прогон"
status={status}
statusTone={accepted ? "success" : "danger"}
metrics={[
{ label: "Timeline", value: `${result.timeline.frameCount}/4 489`, hint: `${result.timeline.availableLidarFrameCount} LiDAR + ${result.timeline.missingLidarFrameCount} explicit UNOBSERVED` },
{ label: "TGS p95 / p99", value: `${number(result.performance.candidateTgsMs.p95, 2)} / ${number(result.performance.candidateTgsMs.p99, 2)} мс`, hint: "чистый candidate stage на CPU" },
{ label: "Completion age p99", value: `${number(result.performance.completionAgeMs.p99, 2)} мс`, hint: "от source timestamp до готового frame result" },
{ label: "Capacity drops", value: String(result.performance.capacityDropCount), hint: "кадры не отбрасывались ради темпа" },
{ label: "Point accounting", value: "100%", hint: `${result.pointAccounting.eligible.toLocaleString("ru-RU")} eligible points` },
]}
conclusion={{
proved: "Полный CPU-only TGS shadow воспроизводимо проходит recorded source clock, сохраняет fail-closed представление и не использует AOS/GPU.",
notProved: "Не приняты visual traversability, модель корпуса, камера-проекция TGS и нагрузка после встраивания в полный realtime world-state graph.",
decision: accepted
? "Кандидат остаётся. Просмотреть полный timeline, затем подключить shadow к realtime graph и измерить общий FPS/latency regression."
: "Кандидат не встраивать; сначала локализовать performance gate, который не прошёл полный replay.",
}}
/>
)}
/>
);
}
@@ -109,6 +109,8 @@ export interface M4ReplayThreatReviewAnchor {
export interface M4ReplayClassifiedSpatialFrame {
sourceSequence: number;
sampleAvailable?: boolean;
sourcePointCount?: number;
pointsMapGravityLocalXyzM: readonly (readonly [number, number, number])[];
pointClassIds: readonly (number | null)[];
cellsMapGravityLocal: readonly {
@@ -129,6 +131,7 @@ export interface M4ReplayClassifiedSpatialLayer {
frame: M4ReplayClassifiedSpatialFrame | null;
loading: boolean;
error: string | null;
replacePointCloud?: boolean;
}
const EMPTY_REVIEW_ANCHORS: readonly M4ReplayThreatReviewAnchor[] = [];
@@ -255,8 +258,8 @@ export function M4ReplayThreatVisual({
if (timelineFrame.activeFrame) lastFrameRef.current = timelineFrame.activeFrame;
const frame = timelineFrame.activeFrame ?? lastFrameRef.current;
useEffect(() => {
onActiveSequenceChange?.(frame?.sequence ?? null);
}, [frame?.sequence, onActiveSequenceChange]);
onActiveSequenceChange?.(timelineFrame.activeSequence);
}, [onActiveSequenceChange, timelineFrame.activeSequence]);
const lastSpatialFrameRef = useRef<{
resultId: string;
frame: M4ThreatTimelineFrame;
@@ -400,16 +403,18 @@ export function M4ReplayThreatVisual({
return status === 2 || status === 3 ? classId : null;
});
}, [semantic, semanticIntegrityError, showSpatialSemantic, spatialFrame, spatialSemanticFrame]);
const classifiedSpatialFrame = !displayingBufferedFrame
&& classifiedSpatialLayer?.frame?.sourceSequence === frame?.sequence
&& spatialFrame?.sequence === frame?.sequence
const activeSpatialFrame = spatialFrame?.sequence === timelineFrame.activeSequence
? spatialFrame
: null;
const classifiedSpatialFrame = classifiedSpatialLayer?.frame?.sourceSequence === timelineFrame.activeSequence
? classifiedSpatialLayer?.frame ?? null
: null;
const replaceClassifiedPointCloud = classifiedSpatialLayer?.replacePointCloud ?? true;
const nominalSensorHeightM = metadata.timeline?.rig.nominalSensorHeightM ?? 0;
const mapGravityLocalSensorToBodyGround = useCallback((
point: readonly [number, number, number],
): readonly [number, number, number] => {
const basis = spatialFrame?.bodyFrame?.basisMapFromBody;
const basis = activeSpatialFrame?.bodyFrame?.basisMapFromBody;
const rotated: readonly [number, number, number] = basis ? [
basis[0][0] * point[0] + basis[1][0] * point[1] + basis[2][0] * point[2],
basis[0][1] * point[0] + basis[1][1] * point[1] + basis[2][1] * point[2],
@@ -418,7 +423,7 @@ export function M4ReplayThreatVisual({
// TGS evidence is translation-only map-gravity-local with the current LiDAR
// as its origin. The metric scene uses the body ground projection as z=0.
return [rotated[0], rotated[1], rotated[2] + nominalSensorHeightM];
}, [nominalSensorHeightM, spatialFrame?.bodyFrame?.basisMapFromBody]);
}, [activeSpatialFrame?.bodyFrame?.basisMapFromBody, nominalSensorHeightM]);
const classifiedPointsBody = useMemo(
() => classifiedSpatialFrame?.pointsMapGravityLocalXyzM.map(
mapGravityLocalSensorToBodyGround,
@@ -776,7 +781,11 @@ export function M4ReplayThreatVisual({
? splitPrimarySize
: 100;
const overlay = metadata.timeline && frame ? (
const overlaySequence = timelineFrame.activeSequence ?? frame?.sequence ?? null;
const overlaySessionSeconds = overlaySequence === null
? null
: (metadata.timeline?.frameTimesNs[overlaySequence] ?? 0) / 1_000_000_000;
const overlay = metadata.timeline && overlaySequence !== null ? (
<div
className="l3-visual-audit__overlay m4-replay-threat-visual__overlay"
style={{
@@ -785,9 +794,9 @@ export function M4ReplayThreatVisual({
>
<div>
<span>RAVNOVES00 · recorded realtime</span>
<strong>frame {frame.sequence + 1}/{metadata.timeline.frameCount}</strong>
<strong>frame {overlaySequence + 1}/{metadata.timeline.frameCount}</strong>
<small>
+{(frame.sessionSeconds - metadata.timeline.timelineStartSeconds).toFixed(3)} с
+{((overlaySessionSeconds ?? metadata.timeline.timelineStartSeconds) - metadata.timeline.timelineStartSeconds).toFixed(3)} с
· {displayingBufferedFrame
? "держим последний кадр, следующий в буфере"
: playbackController.playback.playing ? "воспроизведение" : "пауза / seek"}
@@ -797,24 +806,32 @@ export function M4ReplayThreatVisual({
<span>Spatial evidence</span>
<strong>{classifiedSpatialLayer
? classifiedSpatialFrame
? `${classifiedSpatialFrame.pointsMapGravityLocalXyzM.length.toLocaleString("ru-RU")} TGS points · ${classifiedSpatialFrame.cellsMapGravityLocal.length.toLocaleString("ru-RU")} cells`
? replaceClassifiedPointCloud
? `${classifiedSpatialFrame.pointsMapGravityLocalXyzM.length.toLocaleString("ru-RU")} TGS points · ${classifiedSpatialFrame.cellsMapGravityLocal.length.toLocaleString("ru-RU")} cells`
: `${(activeSpatialFrame?.pointCloudSourceCount ?? classifiedSpatialFrame.sourcePointCount ?? 0).toLocaleString("ru-RU")} source points · ${classifiedSpatialFrame.cellsMapGravityLocal.length.toLocaleString("ru-RU")} TGS cells`
: "TGS spatial buffer"
: `${currentIncrementObstacles.length} current · ${rollingMapObstacles.length} rolling${metadata.timeline.occupancyProvenanceDelivery ? ` · ${lowStepObstacles.length} low-step` : ""}`}</strong>
<small>{classifiedSpatialLayer
? classifiedSpatialFrame
? "map-gravity-local · all eligible points accounted · causal rolling 1 s"
? classifiedSpatialFrame.sampleAvailable === false
? "LiDAR отсутствует · все ячейки принудительно UNOBSERVED · causal rolling 1 s"
: activeSpatialFrame
? "map-gravity-local · all eligible points accounted · causal rolling 1 s"
: "TGS рассчитан · linked source cloud недоступен для этого кадра"
: classifiedSpatialLayer.error ?? `Открываем ${classifiedSpatialLayer.label}`
: (
<>
{spatialFrame
? `${spatialFrame.pointCloudSampleCount}/${spatialFrame.pointCloudSourceCount} exact · ${localSurface.pointsBodyXyzM.length} local SLAM / ${localSurface.sourceFrameCount} frames`
: "квалифицированный spatial frame ещё не получен"}
{frame.worldStateAvailable
? " · world-state delivered"
: ` · world-state gap (${frame.terminalOutcome})`}
{frame
? frame.worldStateAvailable
? " · world-state delivered"
: ` · world-state gap (${frame.terminalOutcome})`
: " · world-state frame unavailable"}
{accumulatedCameraPoints
? ` · camera points ${accumulatedCameraPoints.sampleCount}/${accumulatedCameraPoints.projectedPointCount} · causal ${accumulatedCameraPoints.windowSeconds.toFixed(1)} с / ${accumulatedCameraPoints.sourceFrameCount} frames`
: pointCloudOverlay
: pointCloudOverlay && frame
? ` · camera points ${frame.cameraProjectedSampleCount}/${frame.cameraProjectedPointCount} exact-current · накопление загружается`
: showMediaPoints && cameraPointOverlay.error
? " · накопленное camera cloud недоступно"
@@ -935,12 +952,12 @@ export function M4ReplayThreatVisual({
</div>
</div>
) : null}
{spatialFrame && (!classifiedSpatialLayer || classifiedSpatialFrame) ? (
{(!classifiedSpatialLayer ? spatialFrame : classifiedSpatialFrame) ? (
<LaboratoryMetricEvidenceScene
ref={metricSceneRef}
pointCloudBodyXyzM={classifiedSpatialFrame
pointCloudBodyXyzM={classifiedSpatialFrame && replaceClassifiedPointCloud
? classifiedPointsBody
: spatialFrame.pointCloudBodyXyzM}
: activeSpatialFrame?.pointCloudBodyXyzM ?? []}
localSurfaceBodyXyzM={classifiedSpatialFrame ? [] : localSurface.pointsBodyXyzM}
obstacles={classifiedSpatialFrame ? [] : sceneObstacles}
rig={timeline.rig}
@@ -952,13 +969,13 @@ export function M4ReplayThreatVisual({
showLocalSurface={classifiedSpatialFrame ? false : showLocalSurface}
showRollingMap={showRollingMap}
showLowStep={classifiedSpatialFrame ? false : showLowStep}
pointSemanticClassIds={classifiedSpatialFrame
pointSemanticClassIds={classifiedSpatialFrame && replaceClassifiedPointCloud
? classifiedSpatialFrame.pointClassIds
: alignedSemanticPointIds}
semanticClasses={classifiedSpatialFrame
semanticClasses={classifiedSpatialFrame && replaceClassifiedPointCloud
? classifiedSpatialFrame.classes
: semanticClasses}
semanticPalette={classifiedSpatialFrame
semanticPalette={classifiedSpatialFrame && replaceClassifiedPointCloud
? classifiedSpatialFrame.palette
: semanticPalette}
classifiedCells={classifiedCellsBody}
@@ -979,7 +996,15 @@ export function M4ReplayThreatVisual({
: `${classifiedSpatialLayer.label} рассчитан только на 10 контрольных кадров.`)}</span>
</div>
) : null}
{frame && !frame.spatialAvailable ? (
{classifiedSpatialFrame?.sampleAvailable === false ? (
<div className="m4-replay-threat-visual__pane-status" role="status">
Кадр {classifiedSpatialFrame.sourceSequence + 1}: LiDAR отсутствует; все 2 244 TGS-ячейки явно UNOBSERVED.
</div>
) : classifiedSpatialFrame && !activeSpatialFrame ? (
<div className="m4-replay-threat-visual__pane-status" role="status">
Кадр {classifiedSpatialFrame.sourceSequence + 1}: TGS costmap показан cell-only; linked source cloud для отрисовки отсутствует.
</div>
) : frame && !frame.spatialAvailable ? (
<div className="m4-replay-threat-visual__pane-status" role="status">
{spatialFrame
? `На кадре ${frame.sequence + 1} нет body frame; держим spatial evidence кадра ${spatialFrame.sequence + 1}.`
@@ -112,6 +112,13 @@ const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`
experimentName: "TRAVEL TGS fail-closed traversability evidence",
variantName: "M4.9T4 · 10 anchors · causal rolling 1 s · AOS OFF",
},
"m49-tgs-full-shadow": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Camera + gravity-aligned LiDAR`,
experimentId: "m49-tgs-full-shadow",
experimentName: "TRAVEL TGS complete source-paced shadow",
variantName: "M4.9T5 · 4 489 frames · causal rolling 1 s · CPU-only",
},
"m47-reference-graph-shadow": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Camera + LiDAR dual evidence`,
@@ -26,6 +26,7 @@ function mergeResults(
m48s: next.m48s ?? current.m48s,
m48t: next.m48t ?? current.m48t,
m49Tgs: next.m49Tgs ?? current.m49Tgs,
m49TgsFull: next.m49TgsFull ?? current.m49TgsFull,
m4Threat: next.m4Threat ?? current.m4Threat,
l3: next.l3 ?? current.l3,
l31: next.l31 ?? current.l31,
@@ -126,6 +127,7 @@ export function useAdvancedLaboratoryCatalog({
"m48-static-occupancy-qualification",
"m48r3-static-occupancy-shadow",
"m49-tgs-fail-closed-evidence",
"m49-tgs-full-shadow",
].includes(selectedWorkId)
&& !indexedResultId
) return;