feat(lab): visualize dual evidence replay
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { after, before, test } from "node:test";
|
||||
|
||||
import { createServer } from "vite";
|
||||
|
||||
let server;
|
||||
let fetchM4ThreatReplayResult;
|
||||
let fetchM4ThreatVisual;
|
||||
let fetchM4ThreatVideoOverlay;
|
||||
let selectM4ThreatVideoFrame;
|
||||
|
||||
const resultId = `m4-threat-replay-${"a".repeat(64)}`;
|
||||
|
||||
before(async () => {
|
||||
server = await createServer({
|
||||
appType: "custom",
|
||||
logLevel: "silent",
|
||||
server: { middlewareMode: true },
|
||||
});
|
||||
({
|
||||
fetchM4ThreatReplayResult,
|
||||
fetchM4ThreatVisual,
|
||||
fetchM4ThreatVideoOverlay,
|
||||
selectM4ThreatVideoFrame,
|
||||
} = await server.ssrLoadModule("/src/core/laboratory/m4ReplayThreat.ts"));
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await server?.close();
|
||||
});
|
||||
|
||||
function proposal(overrides = {}) {
|
||||
return {
|
||||
proposal_id: "proposal-1",
|
||||
bbox_xyxy: [100, 120, 240, 360],
|
||||
objectness: 0.91,
|
||||
semantic_hint: "person",
|
||||
occupied_support: false,
|
||||
range_m: null,
|
||||
threat_decision: "unknown",
|
||||
threat_reason_codes: ["camera-only-no-metric-geometry"],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("M4.6 decodes accepted dual-evidence result without physical authority", async () => {
|
||||
const result = await fetchM4ThreatReplayResult({
|
||||
fetcher: async () => new Response(JSON.stringify({
|
||||
schema_version: "missioncore.m4-threat-replay-catalog/v1",
|
||||
items: [{
|
||||
schema_version: "missioncore.m4-threat-replay-view/v1",
|
||||
result_id: resultId,
|
||||
created_at_utc: "2026-08-05T15:36:01.553Z",
|
||||
status: "accepted",
|
||||
profile_id: "m4-ravnoves00-virtual-corridor/v1",
|
||||
rig_profile_id: "virtual-handheld-body-1000x600/v1",
|
||||
corridor_profile_id: "ravnoves00-forward-corridor-8m/v1",
|
||||
source_result_ids: {
|
||||
detector: `m4-detector-replay-${"b".repeat(64)}`,
|
||||
geometry: `m4-geometry-replay-${"c".repeat(64)}`,
|
||||
temporal: `m4-temporal-replay-${"d".repeat(64)}`,
|
||||
},
|
||||
metrics: {
|
||||
decisions: { threat: 8010, "not-threat": 6610, unknown: 60832 },
|
||||
evidence: { "camera-only": 10158, "current-metric": 27299, "stale-or-held": 37995 },
|
||||
fixtures: { critical: 4, critical_false_not_threat: 0, passed: 9, total: 9 },
|
||||
runtime: {
|
||||
frames_per_second: 116.4,
|
||||
provider_latency_p50_ms: 4.3,
|
||||
provider_latency_p95_ms: 19.8,
|
||||
provider_latency_max_ms: 194.3,
|
||||
},
|
||||
reason_counts: { "geometry-only-evidence": 21958 },
|
||||
},
|
||||
configuration: {
|
||||
virtual_body_m: [1, 0.6],
|
||||
nominal_sensor_height_m: 1.25,
|
||||
forward_corridor_m: 8,
|
||||
prediction_horizon_seconds: 5,
|
||||
},
|
||||
limitations: ["replay only"],
|
||||
accepted: true,
|
||||
authority: "replay-simulated",
|
||||
physical_collision_accepted: false,
|
||||
actuation_allowed: false,
|
||||
}],
|
||||
}), { status: 200 }),
|
||||
});
|
||||
assert.equal(result.resultId, resultId);
|
||||
assert.equal(result.metrics.evidence.currentMetric, 27299);
|
||||
assert.equal(result.metrics.fixtures.criticalFalseNotThreat, 0);
|
||||
assert.deepEqual(result.configuration.virtualBodyM, [1, 0.6]);
|
||||
});
|
||||
|
||||
test("M4.6 binds exact CAMERA and metric 3D evidence to one replay frame", async () => {
|
||||
const frame = await fetchM4ThreatVisual(resultId, 1, {
|
||||
fetcher: async () => new Response(JSON.stringify({
|
||||
schema_version: "missioncore.perception-threat-visual-frame/v1",
|
||||
result_id: resultId,
|
||||
ordinal: 1,
|
||||
sequence: 20,
|
||||
frame_id: "frame-000020",
|
||||
source_time_ns: 37421857292,
|
||||
point_cloud_body_xyz_m: [[1, 0, 0.1], [2, 0.2, 0.3]],
|
||||
point_cloud_source_count: 12000,
|
||||
point_cloud_sample_count: 2,
|
||||
metric_obstacles: [{
|
||||
component_id: "temporal-20-1",
|
||||
state: "current",
|
||||
motion: "moving",
|
||||
centroid_body_xyz_m: [2, 0.1, 0.4],
|
||||
cell_centers_body_xyz_m: [[2, 0.1, 0.4]],
|
||||
assessment: {
|
||||
component_id: "temporal-20-1",
|
||||
decision: "threat",
|
||||
corridor_intersection: "intersects",
|
||||
relative_speed_mps: 1.2,
|
||||
closest_approach_m: 0.4,
|
||||
ttc_seconds: 1.6,
|
||||
reason_codes: ["geometry-only-evidence"],
|
||||
},
|
||||
}],
|
||||
camera_proposals: [proposal()],
|
||||
rig: { length_m: 1, width_m: 0.6, nominal_sensor_height_m: 1.25 },
|
||||
corridor: {
|
||||
forward_length_m: 8,
|
||||
rear_margin_m: 0.5,
|
||||
half_width_m: 0.5,
|
||||
prediction_horizon_seconds: 5,
|
||||
},
|
||||
}), { status: 200 }),
|
||||
});
|
||||
assert.equal(frame.sequence, 20);
|
||||
assert.equal(frame.metricObstacles[0].assessment.decision, "threat");
|
||||
assert.equal(frame.cameraProposals[0].threatDecision, "unknown");
|
||||
assert.equal(frame.pointCloudSampleCount, 2);
|
||||
});
|
||||
|
||||
test("M4.6 full video preserves camera-only unknown and nearest-frame selection", async () => {
|
||||
const overlay = await fetchM4ThreatVideoOverlay(resultId, {
|
||||
fetcher: async () => new Response(JSON.stringify({
|
||||
schema_version: "missioncore.m4-threat-video-overlay/v1",
|
||||
result_id: resultId,
|
||||
recorded_source: { session_id: "20260720T065719Z_viewer_live" },
|
||||
image_width: 800,
|
||||
image_height: 600,
|
||||
timeline_start_seconds: 35.421857292,
|
||||
timeline_end_seconds: 484.044857292,
|
||||
frame_count: 4489,
|
||||
frames: [
|
||||
{
|
||||
frame_index: 0,
|
||||
session_seconds: 35.421857292,
|
||||
source_available: true,
|
||||
camera_proposals: [],
|
||||
decision_counts: { threat: 0, "not-threat": 0, unknown: 0 },
|
||||
},
|
||||
{
|
||||
frame_index: 1,
|
||||
session_seconds: 35.521857292,
|
||||
source_available: true,
|
||||
camera_proposals: [proposal()],
|
||||
decision_counts: { threat: 0, "not-threat": 0, unknown: 1 },
|
||||
},
|
||||
],
|
||||
authority: "replay-simulated",
|
||||
}), { status: 200 }),
|
||||
});
|
||||
assert.equal(overlay.frames[1].cameraProposals[0].rangeM, null);
|
||||
assert.equal(selectM4ThreatVideoFrame(overlay.frames, 35.50).frameIndex, 1);
|
||||
});
|
||||
|
||||
test("M4.6 viewer reuses shared video and metric evidence renderers", async () => {
|
||||
const [visual, videoScene, metricScene] = await Promise.all([
|
||||
readFile(new URL("../src/workspaces/laboratory/M4ReplayThreatVisual.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../src/components/laboratory/RecordedEvidenceVideoScene.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../src/components/laboratory/LaboratoryMetricEvidenceScene.tsx", import.meta.url), "utf8"),
|
||||
]);
|
||||
assert.match(visual, /<RecordedEvidenceVideoScene/);
|
||||
assert.match(visual, /<LaboratoryMetricEvidenceScene/);
|
||||
assert.match(visual, /label: "VIDEO"/);
|
||||
assert.match(visual, /label: "CAMERA"/);
|
||||
assert.match(visual, /label: "3D"/);
|
||||
assert.match(videoScene, /<RecordedFmp4Player/);
|
||||
assert.match(metricScene, /OrbitControls/);
|
||||
});
|
||||
Reference in New Issue
Block a user