feat(lab): visualize M4.8R3 system occupancy

This commit is contained in:
DCCONSTRUCTIONS
2026-08-26 13:40:24 +03:00
parent b82a97fe8b
commit 936ee479ed
13 changed files with 641 additions and 7 deletions
@@ -26,6 +26,7 @@ export interface LaboratoryMetricObstacleVisual {
state: "current" | "retained" | "held" | "expired";
centroidBodyXyzM: LaboratoryMetricPoint3;
cellCentersBodyXyzM: readonly LaboratoryMetricPoint3[];
occupancySource?: "baseline" | "mixed" | "additive-low-step";
}
export interface LaboratoryMetricRigVisual {
@@ -41,7 +42,7 @@ export interface LaboratoryMetricCorridorVisual {
}
export interface LaboratoryMetricLegendEntry {
id: LaboratoryMetricDecision | "context" | "local-surface" | "rolling";
id: LaboratoryMetricDecision | "context" | "local-surface" | "rolling" | "low-step";
label: string;
}
@@ -52,6 +53,7 @@ export function laboratoryMetricLegendEntries({
showCurrentIncrement,
showLocalSurface,
showRollingMap,
showLowStep = true,
}: {
pointCloudCount: number;
localSurfaceCount: number;
@@ -59,19 +61,29 @@ export function laboratoryMetricLegendEntries({
showCurrentIncrement: boolean;
showLocalSurface: boolean;
showRollingMap: boolean;
showLowStep?: boolean;
}): readonly LaboratoryMetricLegendEntry[] {
const visibleObstacles = obstacles.filter((obstacle) => (
obstacle.state === "current"
(showLowStep || obstacle.occupancySource === undefined || obstacle.occupancySource === "baseline")
&& (obstacle.state === "current"
? showCurrentIncrement
: obstacle.state === "retained"
? showRollingMap
: false
: false)
));
const decisions = new Set(visibleObstacles.map(({ decision }) => decision));
const entries: LaboratoryMetricLegendEntry[] = [];
if (decisions.has("threat")) entries.push({ id: "threat", label: "Угроза" });
if (decisions.has("not-threat")) entries.push({ id: "not-threat", label: "Вне коридора" });
if (decisions.has("unknown")) entries.push({ id: "unknown", label: "Неизвестно" });
if (
showLowStep
&& visibleObstacles.some((obstacle) => (
obstacle.occupancySource !== undefined && obstacle.occupancySource !== "baseline"
))
) {
entries.push({ id: "low-step", label: "LOW-STEP · добавлено системой" });
}
if (showCurrentIncrement && pointCloudCount > 0) {
entries.push({ id: "context", label: "Текущий кадр" });
}
@@ -153,6 +165,16 @@ function decisionColor(
return tokenColor(host, "--nodedc-warning-rgb", [255, 197, 92]);
}
function obstacleColor(
host: HTMLElement,
obstacle: LaboratoryMetricObstacleVisual,
): THREE.Color {
if (obstacle.occupancySource !== "baseline") {
return tokenColor(host, "--nodedc-accent-rgb", [232, 56, 126]);
}
return decisionColor(host, obstacle.decision);
}
export interface LaboratoryMetricEvidenceSceneHandle {
resetView: () => void;
}
@@ -171,6 +193,7 @@ LaboratoryMetricEvidenceSceneHandle,
showCurrentIncrement: boolean;
showLocalSurface: boolean;
showRollingMap: boolean;
showLowStep?: boolean;
pointSemanticClassIds?: readonly (number | null)[];
semanticClasses?: readonly RecordedEvidenceSemanticClass[];
semanticPalette?: readonly RecordedEvidenceSemanticPaletteEntry[];
@@ -187,6 +210,7 @@ LaboratoryMetricEvidenceSceneHandle,
showCurrentIncrement,
showLocalSurface,
showRollingMap,
showLowStep = true,
pointSemanticClassIds,
semanticClasses,
semanticPalette,
@@ -352,6 +376,12 @@ LaboratoryMetricEvidenceSceneHandle,
for (const obstacle of obstacles) {
if (
(
!showLowStep
&& obstacle.occupancySource !== undefined
&& obstacle.occupancySource !== "baseline"
)
||
(obstacle.state === "current" && !showCurrentIncrement)
|| (obstacle.state === "retained" && !showRollingMap)
|| obstacle.state === "held"
@@ -359,7 +389,7 @@ LaboratoryMetricEvidenceSceneHandle,
) {
continue;
}
const color = decisionColor(host, obstacle.decision);
const color = obstacleColor(host, obstacle);
if (obstacle.state === "retained") {
const geometry = new THREE.BoxGeometry(
occupiedVoxelSizeM * 0.82,
@@ -424,6 +454,7 @@ LaboratoryMetricEvidenceSceneHandle,
showCurrentIncrement,
showLocalSurface,
showRollingMap,
showLowStep,
]);
useEffect(() => {
@@ -538,6 +569,7 @@ LaboratoryMetricEvidenceSceneHandle,
showCurrentIncrement,
showLocalSurface,
showRollingMap,
showLowStep,
});
return (
@@ -43,6 +43,7 @@ import {
} from "./m48ObjectCentricQuality";
import { fetchM48SmallStaticRegression } from "./m48SmallStaticRegression";
import { fetchM48StaticOccupancyQualification } from "./m48StaticOccupancyQualification";
import { fetchM48R3StaticOccupancyShadow } from "./m48r3StaticOccupancyShadow";
import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
@@ -50,6 +51,7 @@ export type AdvancedLaboratoryWorkId =
| "m48-object-centric-quality"
| "m48-small-static-passage-regression"
| "m48-static-occupancy-qualification"
| "m48r3-static-occupancy-shadow"
| "m48s-fixed-class-detector"
| "m48t-risk-quality-temporal"
| "m47-reference-graph-shadow"
@@ -97,6 +99,7 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
"m48-object-centric-quality",
"m48-small-static-passage-regression",
"m48-static-occupancy-qualification",
"m48r3-static-occupancy-shadow",
"m48s-fixed-class-detector",
"m48t-risk-quality-temporal",
"m47-reference-graph-shadow",
@@ -139,6 +142,7 @@ const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
"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",
"m48r3-static-occupancy-shadow": "m48r3-static-occupancy-shadow",
"m48s-fixed-class-detector": "m48s-fixed-class-detector-lab",
"m48t-risk-quality-temporal": "(?:m48t-risk-quality-temporal-lab|m48q-native-risk-quality-lab)",
"m47-reference-graph-shadow": "m47-reference-graph-lab",
@@ -189,6 +193,7 @@ export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
m48: null,
m48SmallStatic: null,
m48StaticOccupancy: null,
m48r3StaticOccupancy: null,
m48s: null,
m48t: null,
m4Threat: null,
@@ -318,6 +323,7 @@ export function advancedLaboratoryResultAvailable(
return 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
: workId === "m48s-fixed-class-detector" ? results.m48s !== null
: workId === "m48t-risk-quality-temporal" ? results.m48t !== null
: workId === "m47-reference-graph-shadow" ? results.m47Graph !== null
@@ -378,6 +384,9 @@ export async function fetchAdvancedLaboratoryResult(
} else if (workId === "m48-static-occupancy-qualification") {
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8R2 qualification identity не выбрана.");
results.m48StaticOccupancy = await fetchM48StaticOccupancyQualification(resultId, { fetcher, signal });
} else if (workId === "m48r3-static-occupancy-shadow") {
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8R3 Worker shadow identity не выбрана.");
results.m48r3StaticOccupancy = await fetchM48R3StaticOccupancyShadow(resultId, { fetcher, signal });
} else if (workId === "m48s-fixed-class-detector") {
if (!resultId) throw new AdvancedLaboratoryContractError("M4.8S LAB identity не выбрана.");
results.m48s = await fetchM48SFixedClassDetectorResult(resultId, { fetcher, signal });
@@ -37,6 +37,7 @@ import type { M47ReferenceGraphLabResult } from "./m47ReferenceGraph";
import type { M48AdvancedResult } from "./m48ObjectCentricQuality";
import type { M48SmallStaticRegressionResult } from "./m48SmallStaticRegression";
import type { M48StaticOccupancyQualificationResult } from "./m48StaticOccupancyQualification";
import type { M48R3StaticOccupancyShadowResult } from "./m48r3StaticOccupancyShadow";
import type { M48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
import type { M48TRiskQualityResult } from "./m48tRiskQuality";
@@ -45,6 +46,7 @@ export interface AdvancedLaboratoryResults {
m48: M48AdvancedResult | null;
m48SmallStatic: M48SmallStaticRegressionResult | null;
m48StaticOccupancy: M48StaticOccupancyQualificationResult | null;
m48r3StaticOccupancy: M48R3StaticOccupancyShadowResult | null;
m48s: M48SFixedClassDetectorResult | null;
m48t: M48TRiskQualityResult | null;
m4Threat: M4ThreatReplayResult | null;
@@ -968,6 +968,7 @@ export async function fetchAdvancedLaboratoryResults({
const e40 = settledCatalogValue(settled[8]);
return {
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
m48r3StaticOccupancy: null,
m48s: null, m48t: null, m4Threat: null,
l3: null, l31: null, l32: null, l33: null,
e31,
@@ -0,0 +1,343 @@
import type { LaboratoryFetch } from "./advancedResults";
import type { M48Authority } from "./m48ObjectCentricQuality";
const RESULT_ID = /^m48r3-static-occupancy-shadow-[a-f0-9]{64}$/;
interface M48R3WorkerPerformance {
admittedFrames: number;
deliveredFrames: number;
fps: number;
worldP95Ms: number;
worldP99Ms: number;
geometryP95Ms: number;
geometryP99Ms: number;
}
export interface M48R3StaticOccupancyShadowResult {
resultId: string;
createdAtUtc: string;
accepted: boolean;
profile: {
id: string;
sha256: string;
minimumPoints: number;
};
metrics: {
frames: { expected: number; baselineDelivered: number; candidateDelivered: number };
performance: {
baseline: M48R3WorkerPerformance;
candidate: M48R3WorkerPerformance;
fpsRegressionFraction: number;
worldStateP95DeltaMs: number;
};
occupancy: {
baselineCellTotal: number;
candidateCellTotal: number;
addedCellTotal: number;
lostCellTotal: number;
baselineComponentTotal: number;
candidateComponentTotal: number;
meanCellGrowthFraction: number;
meanComponentGrowthFraction: number;
maximumAddedCellsPerFrame: number;
maximumCandidateComponentsPerFrame: number;
};
provider: {
additiveObservationCount: number;
additiveVoxelCount: number;
framesWithAdditions: number;
additiveMeanMsPerFrame: number;
peakTemporalComponents: number;
peakRollingCells: number;
};
anchors: {
count: number;
criticalNearCount: number;
criticalNearRecall: number;
matchedCount: number;
canonicalEngineeringRecall: number;
separation: readonly {
displayFrame: number;
expectedMinimumComponents: number;
observedComponents: number;
passed: boolean;
interpretation: string;
}[];
};
capacityDropCount: number;
falseFreeCount: number;
};
gates: Readonly<Record<string, boolean>>;
decision: {
state: "accepted-bounded-worker-shadow" | "rejected-bounded-worker-shadow";
candidateAccepted: boolean;
productionAccepted: false;
nextAction: string;
};
limitations: readonly string[];
authority: M48Authority;
}
export interface M48R3StaticOccupancyCase {
anchorId: string;
displayFrame: number;
sourceSequence: number;
extentXyxy: readonly [number, number, number, number];
distanceBand: "critical-near" | "approach" | "outside-qualified-bands";
componentCount: number;
matched: boolean;
}
export class M48R3StaticOccupancyContractError extends Error {}
function objectValue(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new M48R3StaticOccupancyContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function text(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new M48R3StaticOccupancyContractError(`${label}: ожидалась строка.`);
}
return value;
}
function numberValue(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
throw new M48R3StaticOccupancyContractError(`${label}: ожидалось число.`);
}
return value;
}
function integer(value: unknown, label: string): number {
const parsed = numberValue(value, label);
if (!Number.isSafeInteger(parsed) || parsed < 0) {
throw new M48R3StaticOccupancyContractError(`${label}: ожидалось целое число.`);
}
return parsed;
}
function bool(value: unknown, label: string): boolean {
if (typeof value !== "boolean") {
throw new M48R3StaticOccupancyContractError(`${label}: ожидался флаг.`);
}
return value;
}
function exact(value: unknown, expected: string | boolean, label: string): void {
if (value !== expected) {
throw new M48R3StaticOccupancyContractError(`${label}: нарушен контракт.`);
}
}
function sha256(value: unknown, label: string): string {
const parsed = text(value, label);
if (!/^[a-f0-9]{64}$/.test(parsed)) {
throw new M48R3StaticOccupancyContractError(`${label}: неверный SHA-256.`);
}
return parsed;
}
function extent(value: unknown): readonly [number, number, number, number] {
if (!Array.isArray(value) || value.length !== 4) {
throw new M48R3StaticOccupancyContractError("M4.8R3 extent: нарушен контракт.");
}
const parsed = value.map((item) => numberValue(item, "M4.8R3 extent"));
return [parsed[0]!, parsed[1]!, parsed[2]!, parsed[3]!];
}
function authority(value: unknown): M48Authority {
const row = objectValue(value, "M4.8R3 authority");
exact(row.mode, "replay-simulated", "M4.8R3 authority.mode");
exact(row.physical_live, false, "M4.8R3 authority.physical_live");
exact(row.commands_enabled, false, "M4.8R3 authority.commands_enabled");
exact(row.actuation_allowed, false, "M4.8R3 authority.actuation_allowed");
exact(
row.navigation_or_safety_accepted,
false,
"M4.8R3 authority.navigation_or_safety_accepted",
);
return {
mode: "replay-simulated",
physicalLive: false,
commandsEnabled: false,
actuationAllowed: false,
navigationOrSafetyAccepted: false,
};
}
function workerPerformance(value: unknown, label: string): M48R3WorkerPerformance {
const row = objectValue(value, label);
return {
admittedFrames: integer(row.admitted_frames, `${label}.admitted_frames`),
deliveredFrames: integer(row.delivered_frames, `${label}.delivered_frames`),
fps: numberValue(row.fps, `${label}.fps`),
worldP95Ms: numberValue(row.world_p95_ms, `${label}.world_p95_ms`),
worldP99Ms: numberValue(row.world_p99_ms, `${label}.world_p99_ms`),
geometryP95Ms: numberValue(row.geometry_p95_ms, `${label}.geometry_p95_ms`),
geometryP99Ms: numberValue(row.geometry_p99_ms, `${label}.geometry_p99_ms`),
};
}
export async function fetchM48R3StaticOccupancyShadow(
resultId: string,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<M48R3StaticOccupancyShadowResult> {
if (!RESULT_ID.test(resultId)) {
throw new M48R3StaticOccupancyContractError("M4.8R3 identity недопустима.");
}
const response = await fetcher(
`/api/v1/laboratory/m48r3/static-occupancy/${encodeURIComponent(resultId)}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new M48R3StaticOccupancyContractError(`M4.8R3 недоступен: HTTP ${response.status}.`);
}
const payload = objectValue(await response.json(), "M4.8R3");
exact(
payload.schema_version,
"missioncore.m48r3-static-occupancy-shadow-view/v1",
"M4.8R3 schema",
);
exact(payload.result_id, resultId, "M4.8R3 result");
exact(payload.ground_truth, false, "M4.8R3 ground truth");
const profile = objectValue(payload.profile, "M4.8R3 profile");
const componentization = objectValue(profile.componentization, "M4.8R3 componentization");
const metrics = objectValue(payload.metrics, "M4.8R3 metrics");
const frames = objectValue(metrics.frames, "M4.8R3 frames");
const performance = objectValue(metrics.performance, "M4.8R3 performance");
const occupancy = objectValue(metrics.occupancy, "M4.8R3 occupancy");
const provider = objectValue(metrics.provider, "M4.8R3 provider");
const anchors = objectValue(metrics.assisted_anchors, "M4.8R3 anchors");
if (!Array.isArray(anchors.separation)) {
throw new M48R3StaticOccupancyContractError("M4.8R3 separation: ожидался массив.");
}
const gates = objectValue(payload.gates, "M4.8R3 gates");
const parsedGates = Object.fromEntries(
Object.entries(gates).map(([key, value]) => [key, bool(value, `M4.8R3 gate ${key}`)]),
);
const decision = objectValue(payload.decision, "M4.8R3 decision");
const state = text(decision.state, "M4.8R3 decision.state");
if (state !== "accepted-bounded-worker-shadow" && state !== "rejected-bounded-worker-shadow") {
throw new M48R3StaticOccupancyContractError("M4.8R3 decision.state: неизвестное состояние.");
}
exact(decision.production_accepted, false, "M4.8R3 production acceptance");
if (!Array.isArray(payload.limitations)) {
throw new M48R3StaticOccupancyContractError("M4.8R3 limitations: ожидался массив.");
}
return {
resultId,
createdAtUtc: text(payload.created_at_utc, "M4.8R3 created"),
accepted: bool(payload.accepted, "M4.8R3 accepted"),
profile: {
id: text(profile.id, "M4.8R3 profile id"),
sha256: sha256(profile.sha256, "M4.8R3 profile sha"),
minimumPoints: integer(componentization.minimum_points, "M4.8R3 minimum points"),
},
metrics: {
frames: {
expected: integer(frames.expected, "M4.8R3 expected frames"),
baselineDelivered: integer(frames.baseline_delivered, "M4.8R3 baseline frames"),
candidateDelivered: integer(frames.candidate_delivered, "M4.8R3 candidate frames"),
},
performance: {
baseline: workerPerformance(performance.baseline, "M4.8R3 baseline"),
candidate: workerPerformance(performance.candidate, "M4.8R3 candidate"),
fpsRegressionFraction: numberValue(performance.fps_regression_fraction, "M4.8R3 FPS regression"),
worldStateP95DeltaMs: numberValue(performance.world_state_p95_delta_ms, "M4.8R3 p95 delta"),
},
occupancy: {
baselineCellTotal: integer(occupancy.baseline_cell_total, "M4.8R3 baseline cells"),
candidateCellTotal: integer(occupancy.candidate_cell_total, "M4.8R3 candidate cells"),
addedCellTotal: integer(occupancy.added_cell_total, "M4.8R3 added cells"),
lostCellTotal: integer(occupancy.lost_cell_total, "M4.8R3 lost cells"),
baselineComponentTotal: integer(occupancy.baseline_component_total, "M4.8R3 baseline components"),
candidateComponentTotal: integer(occupancy.candidate_component_total, "M4.8R3 candidate components"),
meanCellGrowthFraction: numberValue(occupancy.mean_cell_growth_fraction, "M4.8R3 cell growth"),
meanComponentGrowthFraction: numberValue(occupancy.mean_component_growth_fraction, "M4.8R3 component growth"),
maximumAddedCellsPerFrame: integer(occupancy.maximum_added_cells_per_frame, "M4.8R3 maximum added cells"),
maximumCandidateComponentsPerFrame: integer(occupancy.maximum_candidate_components_per_frame, "M4.8R3 maximum components"),
},
provider: {
additiveObservationCount: integer(provider.additive_observation_count, "M4.8R3 observations"),
additiveVoxelCount: integer(provider.additive_voxel_count, "M4.8R3 voxels"),
framesWithAdditions: integer(provider.frames_with_additions, "M4.8R3 added frames"),
additiveMeanMsPerFrame: numberValue(provider.additive_mean_ms_per_frame, "M4.8R3 additive mean"),
peakTemporalComponents: integer(provider.peak_temporal_components, "M4.8R3 temporal peak"),
peakRollingCells: integer(provider.peak_rolling_cells, "M4.8R3 rolling peak"),
},
anchors: {
count: integer(anchors.count, "M4.8R3 anchor count"),
criticalNearCount: integer(anchors.critical_near_count, "M4.8R3 near anchors"),
criticalNearRecall: numberValue(anchors.critical_near_recall, "M4.8R3 near recall"),
matchedCount: integer(anchors.matched_count, "M4.8R3 matched anchors"),
canonicalEngineeringRecall: numberValue(anchors.canonical_engineering_recall, "M4.8R3 canonical recall"),
separation: anchors.separation.map((value, index) => {
const row = objectValue(value, `M4.8R3 separation ${index}`);
return {
displayFrame: integer(row.display_frame, "M4.8R3 separation frame"),
expectedMinimumComponents: integer(row.expected_minimum_components, "M4.8R3 expected components"),
observedComponents: integer(row.observed_components, "M4.8R3 observed components"),
passed: bool(row.passed, "M4.8R3 separation passed"),
interpretation: text(row.interpretation, "M4.8R3 separation interpretation"),
};
}),
},
capacityDropCount: integer(metrics.capacity_drop_count, "M4.8R3 capacity drops"),
falseFreeCount: integer(metrics.false_free_count, "M4.8R3 false free"),
},
gates: parsedGates,
decision: {
state,
candidateAccepted: bool(decision.candidate_accepted, "M4.8R3 candidate accepted"),
productionAccepted: false,
nextAction: text(decision.next_action, "M4.8R3 next action"),
},
limitations: payload.limitations.map((value, index) => text(value, `M4.8R3 limitation ${index}`)),
authority: authority(payload.authority),
};
}
export async function fetchM48R3StaticOccupancyCases(
resultId: string,
{ fetcher = fetch, signal }: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
): Promise<readonly M48R3StaticOccupancyCase[]> {
if (!RESULT_ID.test(resultId)) {
throw new M48R3StaticOccupancyContractError("M4.8R3 identity недопустима.");
}
const response = await fetcher(
`/api/v1/laboratory/m48r3/static-occupancy/${encodeURIComponent(resultId)}/cases`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new M48R3StaticOccupancyContractError(`M4.8R3 cases недоступны: HTTP ${response.status}.`);
}
const payload = objectValue(await response.json(), "M4.8R3 cases");
exact(
payload.schema_version,
"missioncore.m48r3-static-occupancy-shadow-cases/v1",
"M4.8R3 cases schema",
);
exact(payload.result_id, resultId, "M4.8R3 cases result");
if (!Array.isArray(payload.cases) || payload.cases.length !== integer(payload.case_count, "M4.8R3 case count")) {
throw new M48R3StaticOccupancyContractError("M4.8R3 cases: нарушен размер.");
}
return payload.cases.map((value, index) => {
const row = objectValue(value, `M4.8R3 case ${index}`);
const distanceBand = text(row.distance_band, "M4.8R3 distance band");
if (distanceBand !== "critical-near" && distanceBand !== "approach" && distanceBand !== "outside-qualified-bands") {
throw new M48R3StaticOccupancyContractError("M4.8R3 distance band: неизвестное значение.");
}
return {
anchorId: text(row.anchor_id, "M4.8R3 anchor id"),
displayFrame: integer(row.display_frame, "M4.8R3 display frame"),
sourceSequence: integer(row.source_sequence, "M4.8R3 source sequence"),
extentXyxy: extent(row.extent_xyxy),
distanceBand,
componentCount: integer(row.component_count, "M4.8R3 component count"),
matched: bool(row.matched, "M4.8R3 matched"),
};
});
}
@@ -1,5 +1,6 @@
export type M4ThreatDecision = "threat" | "not-threat" | "unknown";
export type M4ThreatMotion = "moving" | "stationary" | "unknown";
export type M4OccupancySource = "baseline" | "mixed" | "additive-low-step";
export type M4Point3 = readonly [number, number, number];
export type M4Matrix3 = readonly [M4Point3, M4Point3, M4Point3];
@@ -76,6 +77,7 @@ export interface M4ThreatMetricVisual {
centroidBodyXyzM: M4Point3;
cellCentersBodyXyzM: readonly M4Point3[];
assessment: M4ThreatAssessment;
occupancySource: M4OccupancySource;
}
export interface M4ThreatCameraProposal {
@@ -174,6 +176,7 @@ export interface M4ThreatTimeline {
cameraPointWindowSeconds: number;
cameraPointSampleLimit: number;
worldStateDelivery: "source-paced-latest-wins" | null;
occupancyProvenanceDelivery: "baseline-versus-additive-component-diff" | null;
worldStateFrameCount: number;
supersededFrameCount: number;
sourceRepresentationId: "registered-map-increment-v1";
@@ -331,6 +334,9 @@ function parseMetricVisual(value: unknown): M4ThreatMetricVisual {
) {
throw new M4ThreatContractError("M4.6 temporal state: неизвестное состояние.");
}
const occupancySource = item.occupancy_source === undefined
? "baseline"
: memberOccupancySource(item.occupancy_source);
return {
componentId: text(item.component_id, "M4.6 visual component"),
state,
@@ -340,9 +346,17 @@ function parseMetricVisual(value: unknown): M4ThreatMetricVisual {
(point) => vector(point, 3, "M4.6 cell") as [number, number, number],
),
assessment: parseAssessment(item.assessment),
occupancySource,
};
}
function memberOccupancySource(value: unknown): M4OccupancySource {
if (value !== "baseline" && value !== "mixed" && value !== "additive-low-step") {
throw new M4ThreatContractError("M4.8R3 occupancy source: неизвестное состояние.");
}
return value;
}
export async function fetchM4ThreatReplayResult({
resultId: requestedResultId,
fetcher = fetch,
@@ -683,6 +697,13 @@ export async function fetchM4ThreatTimeline(
"source-paced-latest-wins",
"M4.6 world-state delivery",
),
occupancyProvenanceDelivery: payload.occupancy_provenance_delivery == null
? null
: exact(
payload.occupancy_provenance_delivery,
"baseline-versus-additive-component-diff",
"M4.8R3 occupancy provenance",
),
worldStateFrameCount: payload.world_state_frame_count === undefined
? frameCount
: integer(payload.world_state_frame_count, "M4.6 world-state frames"),
@@ -334,6 +334,12 @@
background: transparent;
}
.laboratory-metric-evidence-scene__legend span[data-decision="low-step"]::before {
box-sizing: border-box;
border: 1px solid rgb(var(--nodedc-foreground-rgb));
background: rgb(var(--nodedc-accent-rgb));
}
.laboratory-metric-evidence-scene__legend span[data-decision="local-surface"]::before {
background: rgb(var(--nodedc-accent-rgb));
opacity: 0.72;
@@ -45,6 +45,7 @@ import { M47ReferenceGraphResultView } from "./M47ReferenceGraphResult";
import { M48ObjectCentricQualityResultView } from "./M48ObjectCentricQualityResult";
import { M48SmallStaticPassageRegressionResultView } from "./M48SmallStaticPassageRegressionResult";
import { M48StaticOccupancyQualificationResultView } from "./M48StaticOccupancyQualificationResult";
import { M48R3StaticOccupancyShadowResultView } from "./M48R3StaticOccupancyShadowResult";
import { M48SFixedClassDetectorResultView } from "./M48SFixedClassDetectorResult";
import { M48TRiskQualityResultView } from "./M48TRiskQualityResult";
@@ -98,6 +99,9 @@ export function AdvancedLaboratoryResult({
if (workId === "m48-static-occupancy-qualification" && results.m48StaticOccupancy) {
return <M48StaticOccupancyQualificationResultView rigLabel={rigLabel} result={results.m48StaticOccupancy} />;
}
if (workId === "m48r3-static-occupancy-shadow" && results.m48r3StaticOccupancy) {
return <M48R3StaticOccupancyShadowResultView rigLabel={rigLabel} result={results.m48r3StaticOccupancy} />;
}
if (workId === "m48s-fixed-class-detector" && results.m48s) {
return <M48SFixedClassDetectorResultView rigLabel={rigLabel} result={results.m48s} />;
}
@@ -0,0 +1,81 @@
import { useEffect, useMemo, useState } from "react";
import { Icon } from "@nodedc/ui-react";
import {
fetchM48R3StaticOccupancyCases,
type M48R3StaticOccupancyCase,
type M48R3StaticOccupancyShadowResult,
} from "../../core/laboratory/m48r3StaticOccupancyShadow";
import {
M4ReplayThreatVisual,
type M4ReplayThreatReviewAnchor,
} from "./M4ReplayThreatVisual";
function message(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "M4.8R3 timeline недоступен.";
}
export function M48R3StaticOccupancyShadowEvidence({
result,
}: {
result: M48R3StaticOccupancyShadowResult;
}) {
const [cases, setCases] = useState<readonly M48R3StaticOccupancyCase[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
setError(null);
void fetchM48R3StaticOccupancyCases(result.resultId, { signal: controller.signal })
.then((nextCases) => {
if (!controller.signal.aborted) setCases(nextCases);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted) setError(message(caught));
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [result.resultId]);
const reviewAnchors = useMemo<readonly M4ReplayThreatReviewAnchor[]>(() => (
cases.map((item) => ({
id: item.anchorId,
sourceSequence: item.sourceSequence,
extentXyxyNormalized: item.extentXyxy,
matchedAtThreshold: item.matched,
}))
), [cases]);
if (loading) {
return (
<div className="l3-visual-audit__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Открываем полный M4.8R3 Worker timeline</span>
</div>
);
}
if (error) {
return (
<div className="l3-visual-audit__state" role="alert">
<Icon name="alert" size={18} />
<span>{error}</span>
</div>
);
}
return (
<M4ReplayThreatVisual
resultId={result.resultId}
reviewAnchors={reviewAnchors}
showReviewAnchorBoxes={false}
reviewLabel="Контрольные кадры M4.8R3 · без ручных рамок"
timelineEndpointRoot="/api/v1/laboratory/m48r3/static-occupancy"
evidenceLabel="M4.8R3"
/>
);
}
@@ -0,0 +1,101 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { M48R3StaticOccupancyShadowResult } from "../../core/laboratory/m48r3StaticOccupancyShadow";
import { M48R3StaticOccupancyShadowEvidence } from "./M48R3StaticOccupancyShadowEvidence";
function number(value: number, digits = 2): string {
return value.toLocaleString("ru-RU", { maximumFractionDigits: digits });
}
function percent(value: number): string {
return `${number(value * 100, 1)}%`;
}
export function M48R3StaticOccupancyShadowResultView({
rigLabel,
result,
}: {
rigLabel: string;
result: M48R3StaticOccupancyShadowResult;
}) {
const candidate = result.metrics.performance.candidate;
const occupancy = result.metrics.occupancy;
const separation = result.metrics.anchors.separation;
const separated = separation.every((item) => item.passed);
const status = result.accepted
? "Полный Worker shadow принят: realtime и раздельные препятствия сохранены"
: "Worker shadow не прошёл один или несколько предобъявленных gate";
const separatedLabel = separation.length
? separation.map((item) => `${item.observedComponents}/${item.expectedMinimumComponents}`).join(" · ")
: "—";
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="M4.8R3 · full Worker static occupancy shadow"
description="Полный 4 489-кадровый прогон проверяет additive low-step occupied-only слой внутри штатного graph pipeline. Камера и RF-DETR не получают дополнительного inference; ручные прямоугольники используются только для перехода к контрольным кадрам и не рисуются как системный результат."
status={status}
statusTone={result.accepted ? "success" : "warning"}
facts={[
{ label: "Конфигурация", value: `${rigLabel} RIGHT · VIDEO/CAMERA/3D/PLAN · LOW-STEP provenance` },
{ label: "Профиль", value: `${result.profile.id} · minimum points ${result.profile.minimumPoints}` },
{ label: "Прогон", value: `${result.metrics.frames.candidateDelivered}/${result.metrics.frames.expected} · immutable ${result.resultId}` },
{ label: "Нагрузка", value: `12 Hz source-paced · ${number(candidate.fps, 3)} effective FPS · +0 inference` },
{ label: "Authority", value: "REPLAY-SIMULATED · production/navigation/actuation OFF" },
]}
brief={{
question: "Можно ли добавить геометрическое обнаружение низких статических препятствий, не разрушив realtime и не склеив отдельные столбики/шары в один объект?",
approach: `Кандидат сравнен покадрово с native baseline на всех ${result.metrics.frames.expected} кадрах. Проверены latency, FPS, рост occupancy, capacity drops, отсутствие потерянных baseline-ячеек и отдельные компоненты на кадре 1856.`,
principalResult: `${number(candidate.fps, 3)} FPS; world-state p95 ${number(candidate.worldP95Ms, 2)} мс; geometry p95/p99 ${number(candidate.geometryP95Ms, 2)}/${number(candidate.geometryP99Ms, 2)} мс; разделение ${separatedLabel}.`,
limitation: "Это воспроизводимый Worker shadow, а не доказательство физической проходимости. Просвет между компонентами сохраняется как геометрия, но допустимость проезда зависит от будущего габарита шасси и отдельного free-space контракта.",
}}
method={{
completeness: "complete",
executionClass: "deterministic",
pipelineId: "m48r3-native-plus-low-step-reference-graph/v1",
components: [
{ kind: "source", name: "native reference graph baseline", version: "M4.7/M4.8R2", role: "immutable occupied/unknown baseline", identitySha256: null },
{ kind: "algorithm", name: "additive low-step occupied-only", version: "v1", role: "CPU geometry; never clearing; no semantic class", identitySha256: result.profile.sha256 },
{ kind: "runtime", name: "full source-paced Worker shadow", version: "4 489 frames", role: "predeclared realtime, growth, separation and safety gates", identitySha256: result.resultId.split("-").at(-1) ?? null },
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="M4.8R3 VISUAL EVIDENCE · SYSTEM COMPONENTS"
title="Полный timeline; LOW-STEP показывает добавленные системой компоненты, ручные рамки скрыты"
kind="recorded-replay"
resizable
>
<M48R3StaticOccupancyShadowEvidence result={result} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Что доказал прогон"
status={status}
statusTone={result.accepted ? "success" : "warning"}
metrics={[
{ label: "Realtime", value: `${number(candidate.fps, 3)} FPS`, hint: `p95 ${number(candidate.worldP95Ms, 2)} мс · gate ≥11,5 FPS / ≤60 мс` },
{ label: "Geometry", value: `${number(candidate.geometryP95Ms, 2)} / ${number(candidate.geometryP99Ms, 2)} мс`, hint: "p95 / p99 · gates 9 / 16 мс" },
{ label: "Occupancy delta", value: `+${occupancy.addedCellTotal.toLocaleString("ru-RU")}`, hint: `${percent(occupancy.meanCellGrowthFraction)} cells · ${percent(occupancy.meanComponentGrowthFraction)} components` },
{ label: "Кадр 1856", value: separated ? `раздельно · ${separatedLabel}` : `не принят · ${separatedLabel}`, hint: "два столбика и две полусферы проверяются отдельными component gates" },
{ label: "Потери / false free", value: `${occupancy.lostCellTotal} / ${result.metrics.falseFreeCount}`, hint: `capacity drops ${result.metrics.capacityDropCount}` },
]}
conclusion={{
proved: `Система сама добавила ${result.metrics.provider.additiveObservationCount.toLocaleString("ru-RU")} low-step observations на ${result.metrics.provider.framesWithAdditions.toLocaleString("ru-RU")} кадрах, сохранила baseline без потерь и выдержала полный realtime shadow.`,
notProved: "Не доказаны физический clearance, planner-authoritative free space, независимые precision/recall и безопасность движения на реальном шасси.",
decision: result.accepted
? "Worker-кандидат принят в ограниченном replay-shadow контуре. Следующий шаг — отдельное решение о cutover и регрессия на новых сценах; ручная разметка не становится runtime-зависимостью."
: "Cutover запрещён. Исправить провалившийся gate и повторить полный immutable shadow без ослабления порогов.",
}}
/>
)}
/>
);
}
@@ -110,12 +110,16 @@ export function M4ReplayThreatVisual({
resultId,
semantic,
reviewAnchors = EMPTY_REVIEW_ANCHORS,
showReviewAnchorBoxes = true,
reviewLabel = "Контрольные примеры M4.8R1",
timelineEndpointRoot,
evidenceLabel = "M4.6",
}: {
resultId: string;
semantic?: M4ReplayThreatSemanticLayer;
reviewAnchors?: readonly M4ReplayThreatReviewAnchor[];
showReviewAnchorBoxes?: boolean;
reviewLabel?: string;
timelineEndpointRoot?: string;
evidenceLabel?: string;
}) {
@@ -124,6 +128,7 @@ export function M4ReplayThreatVisual({
const [showCurrentIncrement, setShowCurrentIncrement] = useState(true);
const [showLocalSurface, setShowLocalSurface] = useState(true);
const [showRollingMap, setShowRollingMap] = useState(true);
const [showLowStep, setShowLowStep] = useState(true);
const [showMediaSemantic, setShowMediaSemantic] = useState(true);
const [showSpatialSemantic, setShowSpatialSemantic] = useState(true);
const [showMediaPoints, setShowMediaPoints] = useState(false);
@@ -262,7 +267,7 @@ export function M4ReplayThreatVisual({
}, [metadata.timeline, resultId, reviewAnchorIdentity, reviewAnchors, seekPlayback, setPlaybackPlaying]);
const reviewAnchorBoxes = useMemo<readonly RecordedEvidenceBox[]>(() => {
const timeline = metadata.timeline;
if (!frame || !timeline) return [];
if (!frame || !timeline || !showReviewAnchorBoxes) return [];
return reviewAnchors
.filter((anchor) => anchor.sourceSequence === frame.sequence)
.map((anchor) => {
@@ -281,7 +286,7 @@ export function M4ReplayThreatVisual({
dashed: true,
};
});
}, [frame, metadata.timeline, reviewAnchors]);
}, [frame, metadata.timeline, reviewAnchors, showReviewAnchorBoxes]);
const activeBoxes = useMemo(
() => [...boxes(frame?.cameraProposals ?? []), ...reviewAnchorBoxes],
[frame, reviewAnchorBoxes],
@@ -345,6 +350,7 @@ export function M4ReplayThreatVisual({
state: obstacle.state,
centroidBodyXyzM: obstacle.centroidBodyXyzM,
cellCentersBodyXyzM: obstacle.cellCentersBodyXyzM,
occupancySource: obstacle.occupancySource,
})) ?? [], [spatialFrame]);
const currentIncrementObstacles = spatialFrame?.metricObstacles.filter(
(item) => item.state === "current",
@@ -352,6 +358,9 @@ export function M4ReplayThreatVisual({
const rollingMapObstacles = spatialFrame?.metricObstacles.filter(
(item) => item.state === "retained",
) ?? [];
const lowStepObstacles = spatialFrame?.metricObstacles.filter(
(item) => item.occupancySource !== "baseline",
) ?? [];
const nearest = spatialFrame?.metricObstacles
.map((item) => item.assessment.closestApproachM)
.filter((value): value is number => value !== null)
@@ -513,6 +522,18 @@ export function M4ReplayThreatVisual({
>
ROLLING
</Button>
{metadata.timeline?.occupancyProvenanceDelivery ? (
<Button
size="compact"
shape="pill"
variant={showLowStep ? "primary" : "secondary"}
aria-pressed={showLowStep}
title="Добавочные occupied-only компоненты low-step; без ручных рамок"
onClick={() => setShowLowStep((visible) => !visible)}
>
LOW-STEP
</Button>
) : null}
{semantic ? (
<Button
size="compact"
@@ -567,7 +588,7 @@ export function M4ReplayThreatVisual({
<Icon name="chevron-right" size={16} />
</IconButton>
<Select
label="Контрольные примеры M4.8R1"
label={reviewLabel}
value={String(selectedReviewAnchorIndex)}
options={reviewAnchors.map((anchor, index) => ({
value: String(index),
@@ -618,6 +639,9 @@ export function M4ReplayThreatVisual({
<span>Spatial evidence</span>
<strong>
{currentIncrementObstacles.length} current · {rollingMapObstacles.length} rolling
{metadata.timeline.occupancyProvenanceDelivery
? ` · ${lowStepObstacles.length} low-step`
: ""}
</strong>
<small>
{spatialFrame
@@ -758,6 +782,7 @@ export function M4ReplayThreatVisual({
showCurrentIncrement={showCurrentIncrement}
showLocalSurface={showLocalSurface}
showRollingMap={showRollingMap}
showLowStep={showLowStep}
pointSemanticClassIds={alignedSemanticPointIds}
semanticClasses={semanticClasses}
semanticPalette={semanticPalette}
@@ -84,6 +84,13 @@ const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`
experimentName: "M4.8 · conservative static occupancy qualification",
variantName: "M4.8R2 · current/rolling + low-step occupied-only candidate",
},
"m48r3-static-occupancy-shadow": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Canonical Reference Graph`,
experimentId: "m48r3-static-occupancy-shadow",
experimentName: "M4.8R3 · full Worker static occupancy shadow",
variantName: "M4.8R3 · 4 489 frames · additive low-step occupied-only",
},
"m48s-fixed-class-detector": {
profileId: "rig-ravnoves-perception-gate-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · RAVNOVES00 perception gate`,
@@ -22,6 +22,7 @@ function mergeResults(
m48: next.m48 ?? current.m48,
m48SmallStatic: next.m48SmallStatic ?? current.m48SmallStatic,
m48StaticOccupancy: next.m48StaticOccupancy ?? current.m48StaticOccupancy,
m48r3StaticOccupancy: next.m48r3StaticOccupancy ?? current.m48r3StaticOccupancy,
m48s: next.m48s ?? current.m48s,
m48t: next.m48t ?? current.m48t,
m4Threat: next.m4Threat ?? current.m4Threat,
@@ -122,6 +123,7 @@ export function useAdvancedLaboratoryCatalog({
"m48-object-centric-quality",
"m48-small-static-passage-regression",
"m48-static-occupancy-qualification",
"m48r3-static-occupancy-shadow",
].includes(selectedWorkId)
&& !indexedResultId
) return;