Files
NODEDC_MISSION_CORE/apps/control-station/test/m4ReplayThreat.test.mjs
T

351 lines
13 KiB
JavaScript

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 fetchM4ThreatTimeline;
let fetchM4ThreatTimelineChunk;
let selectM4ThreatTimelineFrame;
let selectM4ThreatTimelineSequence;
let advanceRecordedEvidencePlayback;
let m4ThreatChunkWindowStarts;
const resultId = `m4-threat-replay-${"a".repeat(64)}`;
before(async () => {
server = await createServer({
appType: "custom",
logLevel: "silent",
server: { middlewareMode: true },
});
({
fetchM4ThreatReplayResult,
fetchM4ThreatVisual,
fetchM4ThreatTimeline,
fetchM4ThreatTimelineChunk,
selectM4ThreatTimelineFrame,
selectM4ThreatTimelineSequence,
} = await server.ssrLoadModule("/src/core/laboratory/m4ReplayThreat.ts"));
({ advanceRecordedEvidencePlayback } = await server.ssrLoadModule(
"/src/components/laboratory/useRecordedEvidencePlayback.ts",
));
({ m4ThreatChunkWindowStarts } = await server.ssrLoadModule(
"/src/workspaces/laboratory/useM4ThreatTimeline.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,
};
}
function timelineFrame(sequence, sessionSeconds, overrides = {}) {
return {
schema_version: "missioncore.recorded-spatial-evidence-frame/v1",
sequence,
frame_id: `frame-${String(sequence).padStart(6, "0")}`,
source_time_ns: Math.round(sessionSeconds * 1_000_000_000),
session_seconds: sessionSeconds,
source_available: true,
spatial_available: true,
point_cloud_body_xyz_m: [[1, 0, 0.1]],
point_cloud_source_count: 1,
point_cloud_sample_count: 1,
point_cloud_layer: "current-increment",
rolling_map_component_count: 0,
metric_obstacles: [],
camera_proposals: [],
decision_counts: { threat: 0, "not-threat": 0, unknown: 0 },
camera_url: `/api/v1/laboratory/m4-threat/results/${resultId}/timeline/frames/${sequence}/camera`,
authority: "replay-simulated",
...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-base-footprint-1000x600/v2",
corridor_profile_id: "ravnoves00-forward-corridor-8m/v2",
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": 28081, "rolling-map-retained": 70989, "stale-or-held": 38025 },
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,
},
body_frame: {
available: 3928,
qualified: 3861,
rejected: 67,
camera_forward_alignment_deg: { p95: 8.439, maximum: 24.252 },
},
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,
body_frame: {
origin: "local-surface-vertical-projection",
up: "vendor-slam-map-gravity-axis",
forward: "smoothed-slam-trajectory-validated-by-camera-axis",
},
},
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, 28081);
assert.equal(result.metrics.fixtures.criticalFalseNotThreat, 0);
assert.equal(result.metrics.bodyFrame.qualified, 3861);
assert.equal(result.metrics.bodyFrame.cameraForwardAlignmentDeg.p95, 8.439);
assert.deepEqual(result.configuration.virtualBodyM, [1, 0.6]);
assert.equal(result.configuration.bodyFrame.up, "vendor-slam-map-gravity-axis");
});
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,
camera_url: `/api/v1/laboratory/m4-threat/results/${resultId}/visuals/1/camera`,
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.match(frame.cameraUrl, /\/visuals\/1\/camera$/);
assert.equal(frame.metricObstacles[0].assessment.decision, "threat");
assert.equal(frame.cameraProposals[0].threatDecision, "unknown");
assert.equal(frame.pointCloudSampleCount, 2);
});
test("M4.6 v2 keeps CURRENT INCREMENT separate from ROLLING MAP", async () => {
const frame = await fetchM4ThreatVisual(resultId, 14, {
fetcher: async () => new Response(JSON.stringify({
schema_version: "missioncore.perception-threat-visual-frame/v2",
result_id: resultId,
camera_url: `/api/v1/laboratory/m4-threat/results/${resultId}/visuals/14/camera`,
ordinal: 14,
sequence: 1880,
frame_id: "frame-001880",
source_time_ns: 223304857292,
point_cloud_body_xyz_m: [[1, 0, 0.1]],
point_cloud_source_count: 1652,
point_cloud_sample_count: 1652,
point_cloud_layer: "current-increment",
rolling_map_component_count: 10,
metric_obstacles: [{
component_id: "rolling-sphere-near",
state: "retained",
motion: "unknown",
centroid_body_xyz_m: [0.76, -0.27, 0.38],
cell_centers_body_xyz_m: [[0.52, -0.3, 0.08]],
assessment: {
component_id: "rolling-sphere-near",
decision: "threat",
corridor_intersection: "intersects",
relative_speed_mps: null,
closest_approach_m: 0.1,
ttc_seconds: null,
reason_codes: ["retained-corridor-intersection"],
},
}],
camera_proposals: [],
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.pointCloudLayer, "current-increment");
assert.equal(frame.rollingMapComponentCount, 10);
assert.equal(frame.metricObstacles[0].state, "retained");
assert.equal(frame.metricObstacles[0].assessment.decision, "threat");
});
test("M4.6 timeline keeps only a compact index and decodes bounded spatial chunks", async () => {
const frameTimesNs = Array.from(
{ length: 4489 },
(_, index) => 35_421_857_292 + index * 100_000_000,
);
const timeline = await fetchM4ThreatTimeline(resultId, {
fetcher: async () => new Response(JSON.stringify({
schema_version: "missioncore.recorded-spatial-evidence-timeline/v1",
result_id: resultId,
recorded_source: {
session_id: "20260720T065719Z_viewer_live",
source_id: "RAVNOVES00",
representation_id: "registered-map-increment-v1",
synchronization: "host-arrival-best-effort",
},
image_width: 800,
image_height: 600,
frame_count: 4489,
frame_times_ns: frameTimesNs,
timeline_start_seconds: 35.421857292,
timeline_end_seconds: 484.221857292,
nominal_frame_interval_seconds: 0.1,
nominal_rate_hz: 10,
max_chunk_frames: 24,
point_sample_limit: 4096,
maximum_source_points_per_frame: 3092,
point_delivery: "exact-current-increment",
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,
occupied_voxel_size_m: 0.45,
prediction_horizon_seconds: 5,
},
authority: "replay-simulated",
}), { status: 200 }),
});
assert.equal(timeline.frameTimesNs.length, 4489);
assert.equal(timeline.pointDelivery, "exact-current-increment");
assert.equal(timeline.maximumSourcePointsPerFrame, 3092);
assert.equal(timeline.occupiedVoxelSizeM, 0.45);
assert.equal(selectM4ThreatTimelineSequence(timeline.frameTimesNs, 35.50), 1);
const chunk = await fetchM4ThreatTimelineChunk(resultId, 0, 2, {
fetcher: async () => new Response(JSON.stringify({
schema_version: "missioncore.recorded-spatial-evidence-chunk/v1",
result_id: resultId,
start_sequence: 0,
frame_count: 2,
next_sequence: 2,
frames: [
timelineFrame(0, 35.421857292),
timelineFrame(1, 35.521857292, {
camera_proposals: [proposal()],
decision_counts: { threat: 0, "not-threat": 0, unknown: 1 },
}),
],
authority: "replay-simulated",
}), { status: 200 }),
});
assert.equal(chunk.frames[1].cameraProposals[0].rangeM, null);
assert.equal(selectM4ThreatTimelineFrame(chunk.frames, 35.50).sequence, 1);
});
test("M4.6 spatial buffering keeps previous, active and two future chunks", () => {
assert.deepEqual(m4ThreatChunkWindowStarts(48, 24, 4489), [24, 48, 72, 96]);
assert.deepEqual(m4ThreatChunkWindowStarts(0, 24, 4489), [0, 24, 48]);
});
test("recorded evidence clock advances by selected rate and stops at the sealed end", () => {
const range = { startSeconds: 10, endSeconds: 20 };
assert.deepEqual(
advanceRecordedEvidencePlayback(
{ currentSeconds: 12, playing: true, rate: 2 },
1.5,
range,
),
{ currentSeconds: 15, playing: true, rate: 2 },
);
assert.deepEqual(
advanceRecordedEvidencePlayback(
{ currentSeconds: 19.5, playing: true, rate: 1 },
1,
range,
),
{ currentSeconds: 20, playing: false, rate: 1 },
);
});
test("M4.6 viewer reuses shared camera, video and metric evidence renderers", async () => {
const [visual, imageScene, videoScene, metricScene] = await Promise.all([
readFile(new URL("../src/workspaces/laboratory/M4ReplayThreatVisual.tsx", import.meta.url), "utf8"),
readFile(new URL("../src/components/laboratory/RecordedEvidenceImageScene.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, /<RecordedEvidenceImageScene/);
assert.match(visual, /<LaboratoryMetricEvidenceScene/);
assert.match(visual, /m4-replay-threat-visual__deck/);
assert.match(visual, /lastFrameRef/);
assert.match(visual, /<ObservationTimeline/);
assert.match(visual, /useM4ThreatTimelineFrame/);
assert.match(visual, /label: "VIDEO"/);
assert.match(visual, /label: "CAMERA"/);
assert.match(visual, /label: "3D"/);
assert.match(videoScene, /<RecordedFmp4Player/);
assert.match(imageScene, /<RecordedEvidenceBoxOverlay/);
assert.match(metricScene, /OrbitControls/);
assert.match(metricScene, /CURRENT INCREMENT/);
assert.match(metricScene, /ROLLING MAP/);
});