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/);
});