refactor(ui): reuse canonical graph for M4.8 regression

This commit is contained in:
DCCONSTRUCTIONS
2026-08-25 00:08:33 +03:00
parent 669bb64423
commit 1084984da2
9 changed files with 322 additions and 261 deletions
@@ -184,6 +184,7 @@
margin-left: auto;
}
.m4-replay-threat-visual__review-controls,
.m4-replay-threat-visual__pane-layer-controls,
.m4-replay-threat-visual__single-pane-controls {
display: flex;
@@ -195,11 +196,14 @@
scrollbar-width: none;
}
.m4-replay-threat-visual__review-controls,
.m4-replay-threat-visual__review-controls > *,
.m4-replay-threat-visual__single-pane-controls,
.m4-replay-threat-visual__single-pane-controls > * {
pointer-events: auto;
}
.m4-replay-threat-visual__review-controls::-webkit-scrollbar,
.m4-replay-threat-visual__pane-layer-controls::-webkit-scrollbar,
.m4-replay-threat-visual__single-pane-controls::-webkit-scrollbar {
display: none;
@@ -5,7 +5,7 @@ import {
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type { M48SmallStaticRegressionResult } from "../../core/laboratory/m48SmallStaticRegression";
import { M48SmallStaticRegressionVisual } from "./M48SmallStaticRegressionVisual";
import { M48SmallStaticRegressionEvidence } from "./M48SmallStaticRegressionEvidence";
function percent(value: number): string {
return `${(value * 100).toLocaleString("ru-RU", { maximumFractionDigits: 1 })}%`;
@@ -30,7 +30,7 @@ export function M48SmallStaticPassageRegressionResultView({
status={status}
statusTone={result.accepted ? "success" : "warning"}
facts={[
{ label: "Конфигурация", value: `${rigLabel} RIGHT · camera + prediction-free current spatial evidence` },
{ label: "Конфигурация", value: `${rigLabel} RIGHT · Canonical Reference Graph · VIDEO/CAMERA/3D/PLAN/SEMANTICS` },
{ label: "Пайплайн", value: result.pipelineId },
{ label: "Эксперимент", value: result.experimentId },
{ label: "Прогон", value: `${result.runLabel} · immutable ${result.resultId}` },
@@ -47,7 +47,8 @@ export function M48SmallStaticPassageRegressionResultView({
executionClass: "deterministic",
pipelineId: result.pipelineId,
components: [
{ kind: "source", name: result.packId, version: "immutable Worker 006 pack", role: "frozen candidate output + exact camera/current-spatial evidence", identitySha256: result.packId.split("-").at(-1) ?? null },
{ kind: "source", name: result.packId, version: "immutable Worker 006 pack", role: "frozen candidate output + exact assisted anchors", identitySha256: result.packId.split("-").at(-1) ?? null },
{ kind: "source", name: result.referenceGraphLabResultId, version: "approved canonical template", role: "synchronized VIDEO/CAMERA/3D/PLAN/SEMANTICS evidence", identitySha256: result.referenceGraphLabResultId.split("-").at(-1) ?? null },
{ kind: "source", name: "M4.8 assisted correction snapshot", version: "revision-bound", role: "operator-added anchors · not independent truth", identitySha256: null },
{ kind: "algorithm", name: "small-static assisted-anchor comparator", version: "v1", role: "exact-frame class-free IoU regression", identitySha256: result.resultId.split("-").at(-1) ?? null },
],
@@ -55,8 +56,8 @@ export function M48SmallStaticPassageRegressionResultView({
/>
)}
evidence={(
<LaboratoryEvidence eyebrow="M4.8R1 VISUAL EVIDENCE · ASSISTED ANCHOR + WORKER" title="Точный кадр: ручной якорь и frozen-ответ Worker 006" kind="recorded-replay" resizable>
<M48SmallStaticRegressionVisual resultId={result.resultId} />
<LaboratoryEvidence eyebrow="M4.8R1 VISUAL EVIDENCE · CANONICAL REFERENCE GRAPH" title="Полный граф и 14 точных assisted-якорей на общем recorded timeline" kind="recorded-replay" resizable>
<M48SmallStaticRegressionEvidence result={result} />
</LaboratoryEvidence>
)}
result={(
@@ -0,0 +1,96 @@
import { useEffect, useMemo, useState } from "react";
import { Icon } from "@nodedc/ui-react";
import { fetchM47ReferenceGraphLab, type M47ReferenceGraphLabResult } from "../../core/laboratory/m47ReferenceGraph";
import {
fetchM48SmallStaticRegressionCases,
type M48SmallStaticRegressionCaseSummary,
type M48SmallStaticRegressionResult,
} from "../../core/laboratory/m48SmallStaticRegression";
import {
M4ReplayThreatVisual,
type M4ReplayThreatReviewAnchor,
} from "./M4ReplayThreatVisual";
function message(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "Каноническое graph evidence M4.8R1 недоступно.";
}
export function M48SmallStaticRegressionEvidence({
result,
}: {
result: M48SmallStaticRegressionResult;
}) {
const [graph, setGraph] = useState<M47ReferenceGraphLabResult | null>(null);
const [cases, setCases] = useState<readonly M48SmallStaticRegressionCaseSummary[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
setError(null);
void Promise.all([
fetchM47ReferenceGraphLab({
resultId: result.referenceGraphLabResultId,
signal: controller.signal,
}),
fetchM48SmallStaticRegressionCases(result.resultId, {
signal: controller.signal,
}),
])
.then(([nextGraph, nextCases]) => {
if (controller.signal.aborted) return;
if (nextCases.some((item) => item.sequence < 1 || item.sequence > nextGraph.frames.expected)) {
throw new Error("M4.8R1 anchor вышел за immutable timeline Canonical Reference Graph.");
}
setGraph(nextGraph);
setCases(nextCases);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted) setError(message(caught));
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [result.referenceGraphLabResultId, result.resultId]);
const reviewAnchors = useMemo<readonly M4ReplayThreatReviewAnchor[]>(() => (
cases.map((item) => ({
id: item.anchorId,
sourceSequence: item.sequence - 1,
extentXyxyNormalized: item.anchorExtentXyxy,
matchedAtThreshold: item.matchedAtThreshold,
}))
), [cases]);
if (loading) {
return (
<div className="l3-visual-audit__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Открываем утверждённый Canonical Reference Graph</span>
</div>
);
}
if (error || !graph) {
return (
<div className="l3-visual-audit__state" role="alert">
<Icon name="alert" size={18} />
<span>{error ?? "Canonical Reference Graph не связан с прогоном M4.8R1."}</span>
</div>
);
}
return (
<M4ReplayThreatVisual
resultId={graph.visual.resultId}
semantic={{
resultId: graph.semantic.resultId,
taxonomy: graph.semantic.taxonomy,
}}
reviewAnchors={reviewAnchors}
/>
);
}
@@ -1,235 +0,0 @@
import { useEffect, useMemo, useState } from "react";
import { Icon, IconButton, StatusBadge } from "@nodedc/ui-react";
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
import {
fetchM48ReviewSourceCatalog,
type M48ReviewTracklet,
} from "../../core/laboratory/m48ObjectCentricQuality";
import {
fetchM48SmallStaticRegressionCase,
fetchM48SmallStaticRegressionCases,
type M48SmallStaticRegressionCase,
type M48SmallStaticRegressionCaseSummary,
} from "../../core/laboratory/m48SmallStaticRegression";
import {
M48BlindClipPlayer,
type M48BlindEvidenceMode,
} from "./annotation/M48BlindClipPlayer";
import { M48EvidenceModeRail } from "./annotation/M48EvidenceModeControls";
import { useM48SpatialClipPlayback } from "./annotation/useM48SpatialClipPlayback";
function message(error: unknown): string {
return error instanceof Error && error.message.trim()
? error.message
: "M4.8R1 evidence недоступно.";
}
function exactFrameTracklets(item: M48SmallStaticRegressionCase): readonly M48ReviewTracklet[] {
const sequence = item.anchor.sequence;
const state = (
objectId: string,
extentXyxy: readonly [number, number, number, number],
geometryAssociation: M48ReviewTracklet["stateSegments"][number]["geometryAssociation"],
freshness: M48ReviewTracklet["stateSegments"][number]["freshness"],
motion: M48ReviewTracklet["stateSegments"][number]["motion"],
threat: M48ReviewTracklet["stateSegments"][number]["threat"],
criticalCorridorObstacle: boolean,
): M48ReviewTracklet => ({
objectId,
firstSequence: sequence,
lastSequence: sequence,
keyframes: [{ sequence, extentXyxy, visibility: "visible" }],
stateSegments: [{
startSequence: sequence,
endSequence: sequence,
geometryAssociation,
freshness,
motion,
threat,
criticalCorridorObstacle,
}],
notes: null,
});
return [
state(
`ASSISTED · ${item.anchor.objectId}`,
item.anchor.extentXyxy,
item.anchor.geometryAssociation,
item.anchor.freshness,
item.anchor.motion,
item.anchor.threat,
item.anchor.requiresAvoidanceOrClearance,
),
...item.comparison.workerObjects.map((object) => state(
`WORKER · ${object.predictionId}`,
object.extentXyxy,
object.geometryAssociation,
object.freshness,
object.motion,
object.threat,
false,
)),
];
}
export function M48SmallStaticRegressionVisual({ resultId }: { resultId: string }) {
const [cases, setCases] = useState<readonly M48SmallStaticRegressionCaseSummary[]>([]);
const [caseIndex, setCaseIndex] = useState(0);
const [item, setItem] = useState<M48SmallStaticRegressionCase | null>(null);
const [catalog, setCatalog] = useState<Awaited<ReturnType<typeof fetchM48ReviewSourceCatalog>> | null>(null);
const [sequence, setSequence] = useState(1);
const [mode, setMode] = useState<M48BlindEvidenceMode>("camera");
const [cameraVisible, setCameraVisible] = useState(true);
const [expanded, setExpanded] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
setError(null);
void fetchM48SmallStaticRegressionCases(resultId, { signal: controller.signal })
.then((next) => {
if (!controller.signal.aborted) setCases(next);
})
.catch((caught: unknown) => !controller.signal.aborted && setError(message(caught)))
.finally(() => !controller.signal.aborted && setLoading(false));
return () => controller.abort();
}, [resultId]);
useEffect(() => {
const selected = cases[caseIndex];
if (!selected) {
setItem(null);
return;
}
const controller = new AbortController();
setLoading(true);
setError(null);
void fetchM48SmallStaticRegressionCase(resultId, selected.anchorId, { signal: controller.signal })
.then((next) => {
if (controller.signal.aborted) return;
setItem(next);
setSequence(next.anchor.sequence);
})
.catch((caught: unknown) => !controller.signal.aborted && setError(message(caught)))
.finally(() => !controller.signal.aborted && setLoading(false));
return () => controller.abort();
}, [caseIndex, cases, resultId]);
useEffect(() => {
if (!item) return;
const controller = new AbortController();
setCatalog(null);
void fetchM48ReviewSourceCatalog(item.packId, { signal: controller.signal })
.then((next) => !controller.signal.aborted && setCatalog(next))
.catch((caught: unknown) => !controller.signal.aborted && setError(message(caught)));
return () => controller.abort();
}, [item?.packId]);
const clip = item && catalog
? catalog.clips.find((candidate) => candidate.clipId === item.anchor.clipId) ?? null
: null;
const spatialEnabled = Boolean(
catalog?.evidenceCapabilities.currentPointCloudBodyXyzM
&& catalog.evidenceCapabilities.rig
&& catalog.evidenceCapabilities.virtualCorridor,
);
const spatial = useM48SpatialClipPlayback({
packId: item?.packId ?? "",
clip,
sequence,
enabled: mode !== "camera" && spatialEnabled,
});
const tracklets = useMemo(
() => item ? exactFrameTracklets(item) : [],
[item],
);
return (
<LaboratoryEvidenceViewer
label="M4.8R1 assisted-anchor regression"
className="m48-atlas-visual"
mode={mode}
modes={[]}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
modeControlsVisible={false}
chromeLayout="stacked"
actions={(
<>
<IconButton
label="Предыдущий assisted-якорь"
disabled={!cases.length}
onClick={() => setCaseIndex((current) => (current - 1 + cases.length) % cases.length)}
>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton
label="Следующий assisted-якорь"
disabled={!cases.length}
onClick={() => setCaseIndex((current) => (current + 1) % cases.length)}
>
<Icon name="chevron-right" size={16} />
</IconButton>
</>
)}
overlay={item ? (
<div className="m48-atlas-visual__case">
<StatusBadge tone={item.comparison.matchedAtThreshold ? "success" : "warning"}>
{item.comparison.matchedAtThreshold ? "WORKER RECALL" : "WORKER MISS"}
</StatusBadge>
<strong>{caseIndex + 1}/{cases.length} · {item.anchor.clipId} · кадр {item.anchor.sequence}</strong>
<small>ASSISTED-якорь, не independent truth · best IoU {item.comparison.bestIou.toFixed(3)}</small>
</div>
) : null}
>
<div className="m48-evidence-stage">
{loading ? (
<div className="m48-atlas-visual__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
Загружаем M4.8R1 bounded case
</div>
) : error ? (
<div className="m48-atlas-visual__state" role="alert"><Icon name="alert" size={18} />{error}</div>
) : clip && catalog?.cameraPlayback && item ? (
<M48BlindClipPlayer
cameraPlayback={catalog.cameraPlayback}
clip={clip}
sequence={sequence}
mode={mode}
cameraVisible={cameraVisible}
tracklets={tracklets}
selectedObjectId={`ASSISTED · ${item.anchor.objectId}`}
editable={false}
drawing={false}
spatialFrame={spatial.frame}
spatialLoading={spatial.loading}
spatialError={spatial.error}
spatialEvidenceAvailable={spatialEnabled}
onSequenceChange={setSequence}
onDrawingChange={() => undefined}
onSelectedObjectIdChange={() => undefined}
onTrackletsChange={() => undefined}
/>
) : (
<div className="m48-atlas-visual__state" role="status">
<Icon name="alert" size={18} />Точный источник M4.8R1 недоступен.
</div>
)}
{catalog ? (
<M48EvidenceModeRail
mode={mode}
cameraVisible={cameraVisible}
spatialAvailable={spatialEnabled}
onModeChange={setMode}
onCameraVisibleChange={setCameraVisible}
/>
) : null}
</div>
</LaboratoryEvidenceViewer>
);
}
@@ -3,6 +3,7 @@ import {
Button,
Icon,
IconButton,
Select,
SegmentedControl,
SplitPane,
type SplitPaneOrientation,
@@ -90,12 +91,23 @@ export interface M4ReplayThreatSemanticLayer {
taxonomy: readonly E47SemanticClass[];
}
export interface M4ReplayThreatReviewAnchor {
id: string;
sourceSequence: number;
extentXyxyNormalized: readonly [number, number, number, number];
matchedAtThreshold: boolean;
}
const EMPTY_REVIEW_ANCHORS: readonly M4ReplayThreatReviewAnchor[] = [];
export function M4ReplayThreatVisual({
resultId,
semantic,
reviewAnchors = EMPTY_REVIEW_ANCHORS,
}: {
resultId: string;
semantic?: M4ReplayThreatSemanticLayer;
reviewAnchors?: readonly M4ReplayThreatReviewAnchor[];
}) {
const [mediaMode, setMediaMode] = useState<M4ThreatMediaMode | null>("video");
const [spatialMode, setSpatialMode] = useState<LaboratoryMetricSceneMode | null>(null);
@@ -111,6 +123,7 @@ export function M4ReplayThreatVisual({
: "vertical"
));
const [expanded, setExpanded] = useState(false);
const [selectedReviewAnchorIndex, setSelectedReviewAnchorIndex] = useState(0);
const metricSceneRef = useRef<LaboratoryMetricEvidenceSceneHandle | null>(null);
const metadata = useM4ThreatTimelineMetadata(resultId);
const playbackRange = useMemo(() => metadata.timeline ? ({
@@ -120,6 +133,8 @@ export function M4ReplayThreatVisual({
const playbackController = useRecordedEvidencePlayback(playbackRange, {
clock: mediaMode === "video" ? "external" : "animation",
});
const seekPlayback = playbackController.seek;
const setPlaybackPlaying = playbackController.setPlaying;
const timelineFrame = useM4ThreatTimelineFrame({
resultId,
timeline: metadata.timeline,
@@ -209,7 +224,50 @@ export function M4ReplayThreatVisual({
&& timelineFrame.activeSequence !== null
&& frame.sequence !== timelineFrame.activeSequence,
);
const activeBoxes = useMemo(() => boxes(frame?.cameraProposals ?? []), [frame]);
const reviewAnchorIdentity = useMemo(
() => reviewAnchors.map((anchor) => `${anchor.id}:${anchor.sourceSequence}`).join("|"),
[reviewAnchors],
);
const initializedReviewAnchorRef = useRef("");
useEffect(() => {
const timeline = metadata.timeline;
if (!timeline || !reviewAnchors.length) return;
const identity = `${resultId}:${reviewAnchorIdentity}`;
if (initializedReviewAnchorRef.current === identity) return;
initializedReviewAnchorRef.current = identity;
setSelectedReviewAnchorIndex(0);
const startTimeNs = timeline.frameTimesNs[reviewAnchors[0]!.sourceSequence];
if (typeof startTimeNs === "number") {
setPlaybackPlaying(false);
seekPlayback(startTimeNs / 1_000_000_000);
}
}, [metadata.timeline, resultId, reviewAnchorIdentity, reviewAnchors, seekPlayback, setPlaybackPlaying]);
const reviewAnchorBoxes = useMemo<readonly RecordedEvidenceBox[]>(() => {
const timeline = metadata.timeline;
if (!frame || !timeline) return [];
return reviewAnchors
.filter((anchor) => anchor.sourceSequence === frame.sequence)
.map((anchor) => {
const [left, top, right, bottom] = anchor.extentXyxyNormalized;
return {
boxXyxy: [
left * timeline.imageWidth,
top * timeline.imageHeight,
right * timeline.imageWidth,
bottom * timeline.imageHeight,
] as const,
label: anchor.matchedAtThreshold
? "РУЧНОЙ ЯКОРЬ · ПОКРЫТ WORKER 006"
: "РУЧНОЙ ЯКОРЬ · ПРОПУСК WORKER 006",
tone: anchor.matchedAtThreshold ? "success" as const : "danger" as const,
dashed: true,
};
});
}, [frame, metadata.timeline, reviewAnchors]);
const activeBoxes = useMemo(
() => [...boxes(frame?.cameraProposals ?? []), ...reviewAnchorBoxes],
[frame, reviewAnchorBoxes],
);
const semanticClasses = useMemo<readonly RecordedEvidenceSemanticClass[]>(
() => semantic?.taxonomy.map((item) => ({
id: item.classId,
@@ -429,11 +487,54 @@ export function M4ReplayThreatVisual({
? spatialMode ? spatialLayerControls : mediaMode ? mediaLayerControls : null
: null;
const actions = singlePaneControls ? (
const selectReviewAnchor = (index: number) => {
const timeline = metadata.timeline;
if (!timeline || !reviewAnchors.length) return;
const normalizedIndex = (index + reviewAnchors.length) % reviewAnchors.length;
const anchor = reviewAnchors[normalizedIndex]!;
const sourceTimeNs = timeline.frameTimesNs[anchor.sourceSequence];
if (typeof sourceTimeNs !== "number") return;
setSelectedReviewAnchorIndex(normalizedIndex);
setPlaybackPlaying(false);
seekPlayback(sourceTimeNs / 1_000_000_000);
};
const reviewControls = reviewAnchors.length ? (
<div className="m4-replay-threat-visual__review-controls">
<IconButton
label="Предыдущий контрольный пример"
onClick={() => selectReviewAnchor(selectedReviewAnchorIndex - 1)}
>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton
label="Следующий контрольный пример"
onClick={() => selectReviewAnchor(selectedReviewAnchorIndex + 1)}
>
<Icon name="chevron-right" size={16} />
</IconButton>
<Select
label="Контрольные примеры M4.8R1"
value={String(selectedReviewAnchorIndex)}
options={reviewAnchors.map((anchor, index) => ({
value: String(index),
label: `${index + 1}/${reviewAnchors.length} · кадр ${anchor.sourceSequence + 1} · ${anchor.matchedAtThreshold ? "покрыт" : "пропуск"}`,
}))}
variant="split"
menuWidth="anchor"
onChange={(value) => selectReviewAnchor(Number(value))}
/>
</div>
) : null;
const actions = reviewControls || singlePaneControls ? (
<div className="l3-visual-audit__actions">
<div className="m4-replay-threat-visual__single-pane-controls">
{singlePaneControls}
</div>
{reviewControls}
{singlePaneControls ? (
<div className="m4-replay-threat-visual__single-pane-controls">
{singlePaneControls}
</div>
) : null}
</div>
) : undefined;
@@ -72,7 +72,7 @@ const KNOWN_WORKS: Readonly<Record<Exclude<LaboratoryWorkId, `session:${string}`
},
"m48-small-static-passage-regression": {
profileId: "rig-dual-evidence-virtual-corridor-v1",
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Camera + prediction-free spatial evidence`,
profileName: (rigLabel) => `${rig(rigLabel)} RIGHT · Canonical Reference Graph`,
experimentId: "m48-small-static-passage-regression",
experimentName: "M4.8 · small static passage regression",
variantName: "M4.8R1 · Worker 006 small-static assisted baseline",
@@ -1,5 +1,5 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import { after, before, test } from "node:test";
import { createServer } from "vite";
@@ -10,6 +10,7 @@ let fetchM48SmallStaticRegressionCase;
const resultId = `m48-small-static-passage-regression-${"a".repeat(64)}`;
const packId = `m48-object-quality-pack-${"b".repeat(64)}`;
const referenceGraphLabResultId = `m47-reference-graph-lab-${"d".repeat(64)}`;
const anchorId = `anchor-${"c".repeat(24)}`;
const authority = {
mode: "replay-simulated",
@@ -40,6 +41,7 @@ test("M4.8R1 keeps pipeline identity and assisted evidence authority explicit",
schema_version: "missioncore.m48-small-static-passage-regression-result-view/v1",
result_id: resultId,
pack_id: packId,
reference_graph_lab_result_id: referenceGraphLabResultId,
created_at_utc: "2026-08-24T12:00:00Z",
run_label: "M4.8R1",
pipeline_id: "m48-class-free-object-quality/v1",
@@ -73,6 +75,7 @@ test("M4.8R1 keeps pipeline identity and assisted evidence authority explicit",
});
assert.equal(summary.pipelineId, "m48-class-free-object-quality/v1");
assert.equal(summary.experimentId, "m48-small-static-passage-regression/v1");
assert.equal(summary.referenceGraphLabResultId, referenceGraphLabResultId);
assert.equal(summary.metrics.workerMissedAnchorCount, 14);
assert.equal(summary.independentTruth, false);
});
@@ -86,6 +89,7 @@ test("M4.8R1 bounded case binds one exact assisted anchor and frozen objects", a
anchor_id: anchorId,
clip_id: "m48-clip-03",
sequence: 256,
anchor_extent_xyxy: [0.2, 0.3, 0.4, 0.7],
requires_avoidance_or_clearance: true,
worker_candidate_count: 1,
best_iou: 0.01,
@@ -98,6 +102,7 @@ test("M4.8R1 bounded case binds one exact assisted anchor and frozen objects", a
}),
});
assert.equal(cases[0].outcome, "missed-assisted-anchor");
assert.deepEqual(cases[0].anchorExtentXyxy, [0.2, 0.3, 0.4, 0.7]);
const item = await fetchM48SmallStaticRegressionCase(resultId, anchorId, {
fetcher: async () => response({
@@ -150,14 +155,26 @@ test("M4.8R1 bounded case binds one exact assisted anchor and frozen objects", a
assert.equal(item.groundTruth, false);
});
test("M4.8R1 visual reuses the held viewer and keeps manual boxes exact-frame only", () => {
const visual = readFileSync(
new URL("../src/workspaces/laboratory/M48SmallStaticRegressionVisual.tsx", import.meta.url),
test("M4.8R1 deletes the third viewer and projects anchors through the canonical graph", () => {
const deletedVisual = new URL(
"../src/workspaces/laboratory/M48SmallStaticRegressionVisual.tsx",
import.meta.url,
);
assert.equal(existsSync(deletedVisual), false);
const evidence = readFileSync(
new URL("../src/workspaces/laboratory/M48SmallStaticRegressionEvidence.tsx", import.meta.url),
"utf8",
);
assert.match(visual, /<LaboratoryEvidenceViewer/);
assert.match(visual, /<M48BlindClipPlayer/);
assert.match(visual, /<M48EvidenceModeRail/);
assert.match(visual, /firstSequence: sequence,[\s\S]*lastSequence: sequence/);
assert.doesNotMatch(visual, /Rerun|MediaSource|setInterval/);
const canonicalVisual = readFileSync(
new URL("../src/workspaces/laboratory/M4ReplayThreatVisual.tsx", import.meta.url),
"utf8",
);
assert.match(evidence, /fetchM47ReferenceGraphLab/);
assert.match(evidence, /<M4ReplayThreatVisual/);
assert.match(evidence, /sourceSequence: item\.sequence - 1/);
assert.doesNotMatch(evidence, /M48BlindClipPlayer|M48EvidenceModeRail|Rerun|MediaSource/);
assert.match(canonicalVisual, /reviewAnchors/);
assert.match(canonicalVisual, /РУЧНОЙ ЯКОРЬ · ПРОПУСК WORKER 006/);
assert.match(canonicalVisual, /showSpatialSemantic/);
assert.match(canonicalVisual, /showRollingMap/);
});
@@ -160,6 +160,29 @@ history of rejected approaches belong in Ops.
Primary visual evidence is hosted in one reusable viewer frame.
### Frozen Milestone 4 perception instruments
Milestone 4 admits exactly two operator instruments inside the shared LAB page
template:
1. the M4.8 assisted-correction instrument for editing frozen Worker 006
proposals and adding exact-frame misses;
2. the M4.7 Canonical Reference Graph instrument for synchronized
`VIDEO/CAMERA/3D/PLAN/SEMANTICS`, Worker proposals, current LiDAR, bounded
local SLAM, rolling occupancy and metric-obstacle inspection.
A later M4.8 regression or quality run supplies typed case navigation, manual
anchor overlays and result copy to one of these instruments. It does not create
an experiment-named player, viewer, Rerun configuration, timeline, splitter,
spatial renderer or loading grammar. A manual exact-frame annotation is shown
only on the annotated frame unless a separately evidenced and reviewable
tracking result exists; ordinary model proposals and graph layers continue on
their own full recorded timeline.
A third perception instrument is a product-surface change. It requires explicit
product-owner agreement before implementation and cannot be introduced by a LAB
adapter, result component or experiment configuration.
The viewer frame must:
- provide the canonical expand/restore `IconButton`;
@@ -1384,12 +1384,66 @@ clearance and passability remain functions of admitted LiDAR/local occupancy,
uncertainty and the configured vehicle footprint. Physical live, navigation,
commands, actuation and collision-safety authority remain false.
The regression reuses the held M4.8 recorded viewer and its CAMERA/3D/PLAN,
single media clock, timeline and bounded spatial cache. Each assisted anchor and
the frozen Worker objects are shown only on their exact source frame; the UI does
not drift a manually drawn rectangle across subsequent frames or fabricate a
track. A later Worker candidate must publish another immutable M4.8R run against
the frozen seed, leaving this baseline available for before/after comparison.
The regression reuses the accepted M4.7 Canonical Reference Graph instrument,
not an experiment-local M4.8 viewer. It therefore retains the complete
VIDEO/CAMERA/3D/PLAN/SEMANTICS graph, one media clock, Worker proposals, current
LiDAR, bounded local SLAM, rolling occupancy and metric obstacles. Each assisted
anchor is an additional exact-frame overlay and is shown only on its source
frame; the UI does not drift a manually drawn rectangle across subsequent
frames or fabricate a track. A later Worker candidate must publish another
immutable M4.8R run against the frozen seed, leaving this baseline available for
before/after comparison.
### 2026-08-25 — perception interface freeze and dataset/training boundary
Mission Core now has two and only two admitted Milestone 4 perception
instruments:
1. the M4.8 assisted-correction instrument used to accept, edit, delete and add
class-free exact-frame boxes over frozen Worker 006 proposals;
2. the M4.7 Canonical Reference Graph instrument used to inspect detector,
camera, semantic, LiDAR, local-SLAM, rolling and threat evidence on one full
recorded timeline.
`M48SmallStaticRegressionVisual` is removed. M4.8R results bind to the immutable
M4.7 LAB identity stored in their source pack and project only typed regression
anchors and navigation into the second instrument. Creating another player or
viewer for an experiment is prohibited unless the product owner first approves
a third reusable instrument. This is enforced by the M4.8R frontend contract
test as well as the product UI canon.
Worker 006 remains an inference-serving boundary. Its frozen `5,236` M4.8 boxes
are model output, not an online-learning state. `0/14` in M4.8R1 means that none
of the fourteen operator-added miss anchors reached IoU `0.50` against those
frozen boxes; it does not mean that Worker 006 emitted no detections.
The assisted-correction instrument is a valid annotation front end but is not,
by itself, a train-ready dataset system. A released camera-obstacle dataset must
add the following immutable control plane around its corrections:
- exact source frame, raw/corrected optical representation, calibration,
preprocessing and frozen model identities;
- explicit annotation completeness and negative-frame state rather than
treating absence of a box as background truth;
- revisioned reviewer and adjudication identity, with candidate-assisted data
kept distinct from independent truth;
- route/time-block train, development and sealed validation partitions to
prevent adjacent-frame leakage;
- one versioned task taxonomy. The first task is class-free
`obstacle-presence`; `critical_corridor_obstacle` remains a planning/clearance
attribute and does not become a detector-class-specific collider rule;
- a content-addressed internal manifest plus COCO detection export and hashes.
A lossy YOLO text folder is not the source of truth.
The already deployed CVAT `v2.70.0` remains the offline review/export control
plane behind this contract; it is not another Mission Core evidence viewer.
Mission Core owns the two product instruments and immutable LAB publication.
Training runs in a separate bounded Worker job/container, initially reusing the
current YOLOX/PyTorch family, and may not mutate the serving Triton repository
or the frozen Worker 006 process in place. A successful job seals weights,
configuration, dataset release, metrics and ONNX identity; only then is a
candidate loaded through the existing Triton seam and measured by a new
append-only M4.8R run plus an independent validation gate.
## Implementation order