822 lines
31 KiB
JavaScript
822 lines
31 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 fetchM4ThreatCameraPointOverlay;
|
|
let selectM4ThreatTimelineFrame;
|
|
let selectM4ThreatTimelineSequence;
|
|
let advanceRecordedEvidencePlayback;
|
|
let synchronizeRecordedEvidencePlayback;
|
|
let m4ThreatChunkWindowStarts;
|
|
let cancelM4ThreatChunkRequestsOutsideWindow;
|
|
let buildM4LocalSurface;
|
|
let buildM4StaticObstacleBoxes;
|
|
|
|
const resultId = `m4-threat-replay-${"a".repeat(64)}`;
|
|
|
|
before(async () => {
|
|
server = await createServer({
|
|
appType: "custom",
|
|
logLevel: "silent",
|
|
server: { middlewareMode: true },
|
|
});
|
|
({
|
|
fetchM4ThreatReplayResult,
|
|
fetchM4ThreatVisual,
|
|
fetchM4ThreatTimeline,
|
|
fetchM4ThreatTimelineChunk,
|
|
fetchM4ThreatCameraPointOverlay,
|
|
selectM4ThreatTimelineFrame,
|
|
selectM4ThreatTimelineSequence,
|
|
} = await server.ssrLoadModule("/src/core/laboratory/m4ReplayThreat.ts"));
|
|
({
|
|
advanceRecordedEvidencePlayback,
|
|
synchronizeRecordedEvidencePlayback,
|
|
} = await server.ssrLoadModule(
|
|
"/src/components/laboratory/useRecordedEvidencePlayback.ts",
|
|
));
|
|
({
|
|
m4ThreatChunkWindowStarts,
|
|
cancelM4ThreatChunkRequestsOutsideWindow,
|
|
} = await server.ssrLoadModule(
|
|
"/src/workspaces/laboratory/useM4ThreatTimeline.ts",
|
|
));
|
|
({ buildM4LocalSurface } = await server.ssrLoadModule(
|
|
"/src/core/laboratory/m4LocalSurface.ts",
|
|
));
|
|
({ buildM4StaticObstacleBoxes } = await server.ssrLoadModule(
|
|
"/src/workspaces/laboratory/m4StaticObstacleBoxes.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,
|
|
body_frame: {
|
|
origin_map_xyz_m: [sequence, 0, 0],
|
|
basis_map_from_body: [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
|
|
},
|
|
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",
|
|
local_surface_visualization: {
|
|
derivation: "bounded-registered-increment-accumulation",
|
|
window_seconds: 2,
|
|
voxel_size_m: 0.1,
|
|
radius_m: 12,
|
|
point_limit: 20000,
|
|
authority: "visual-derived",
|
|
},
|
|
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(timeline.localSurfaceVisualization.windowSeconds, 2);
|
|
assert.equal(timeline.localSurfaceVisualization.authority, "visual-derived");
|
|
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.8S timeline binds factory-KB4 camera points through its exact endpoint", async () => {
|
|
const replayResultId = `m48s-fixed-class-detector-lab-${"b".repeat(64)}`;
|
|
const endpointRoot = "/api/v1/laboratory/m48s/fixed-class-detector";
|
|
const frameTimesNs = Array.from(
|
|
{ length: 4489 },
|
|
(_, index) => 35_421_857_292 + index * 100_000_000,
|
|
);
|
|
let requested = "";
|
|
const timeline = await fetchM4ThreatTimeline(replayResultId, {
|
|
endpointRoot,
|
|
fetcher: async (input) => {
|
|
requested = String(input);
|
|
return new Response(JSON.stringify({
|
|
schema_version: "missioncore.recorded-spatial-evidence-timeline/v1",
|
|
result_id: replayResultId,
|
|
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",
|
|
camera_point_delivery: "factory-kb4-causal-registered-accumulation",
|
|
camera_point_window_seconds: 2,
|
|
camera_point_sample_limit: 20000,
|
|
world_state_delivery: "source-paced-latest-wins",
|
|
occupancy_provenance_delivery: "baseline-versus-additive-component-diff",
|
|
camera_obstacle_projection_delivery: "factory-kb4-actionable-added-corridor-bounds",
|
|
world_state_frame_count: 4481,
|
|
superseded_frame_count: 8,
|
|
local_surface_visualization: {
|
|
derivation: "bounded-registered-increment-accumulation",
|
|
window_seconds: 2,
|
|
voxel_size_m: 0.1,
|
|
radius_m: 12,
|
|
point_limit: 20000,
|
|
authority: "visual-derived",
|
|
},
|
|
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(requested, `${endpointRoot}/${replayResultId}/timeline`);
|
|
assert.equal(timeline.cameraPointDelivery, "factory-kb4-causal-registered-accumulation");
|
|
assert.equal(timeline.cameraPointWindowSeconds, 2);
|
|
assert.equal(timeline.worldStateFrameCount, 4481);
|
|
assert.equal(timeline.supersededFrameCount, 8);
|
|
assert.equal(
|
|
timeline.cameraObstacleProjectionDelivery,
|
|
"factory-kb4-actionable-added-corridor-bounds",
|
|
);
|
|
|
|
const chunk = await fetchM4ThreatTimelineChunk(replayResultId, 1, 1, {
|
|
endpointRoot,
|
|
cameraObstacleProjectionDelivery: timeline.cameraObstacleProjectionDelivery,
|
|
fetcher: async (input) => {
|
|
requested = String(input);
|
|
return new Response(JSON.stringify({
|
|
schema_version: "missioncore.recorded-spatial-evidence-chunk/v1",
|
|
result_id: replayResultId,
|
|
start_sequence: 1,
|
|
frame_count: 1,
|
|
next_sequence: 2,
|
|
frames: [timelineFrame(1, 35.521857292, {
|
|
world_state_available: false,
|
|
terminal_outcome: "superseded",
|
|
camera_projected_points_xyd: [[100.5, 200.25, 3.75]],
|
|
camera_projected_source_count: 847,
|
|
camera_projected_point_count: 1,
|
|
camera_projected_sample_count: 1,
|
|
camera_projection: "factory-kb4-exact",
|
|
metric_obstacles: [{
|
|
component_id: "temporal-1855-7",
|
|
state: "current",
|
|
motion: "stationary",
|
|
centroid_body_xyz_m: [2.4, 0.1, 0.3],
|
|
cell_centers_body_xyz_m: [[2.4, 0.1, 0.3]],
|
|
occupancy_source: "additive-low-step",
|
|
camera_projection: {
|
|
bbox_xyxy: [390.5, 280.25, 408.75, 318.5],
|
|
nearest_depth_m: 2.3,
|
|
projected_cell_count: 1,
|
|
projection: "factory-kb4-occupied-voxel-bounds",
|
|
authority: "visual-derived",
|
|
},
|
|
assessment: {
|
|
component_id: "temporal-1855-7",
|
|
decision: "threat",
|
|
corridor_intersection: "intersects",
|
|
relative_speed_mps: null,
|
|
closest_approach_m: 0.2,
|
|
ttc_seconds: null,
|
|
reason_codes: ["current-corridor-intersection"],
|
|
},
|
|
}],
|
|
camera_url: `${endpointRoot}/${replayResultId}/timeline/frames/1/camera`,
|
|
})],
|
|
authority: "replay-simulated",
|
|
}), { status: 200 });
|
|
},
|
|
});
|
|
assert.match(requested, new RegExp(`^${endpointRoot}/${replayResultId}/timeline/chunk`));
|
|
assert.match(
|
|
requested,
|
|
/obstacle_projection=factory-kb4-actionable-added-corridor-bounds/,
|
|
);
|
|
assert.equal(chunk.frames[0].worldStateAvailable, false);
|
|
assert.equal(chunk.frames[0].terminalOutcome, "superseded");
|
|
assert.deepEqual(chunk.frames[0].cameraProjectedPointsXyd[0], [100.5, 200.25, 3.75]);
|
|
assert.equal(chunk.frames[0].cameraProjection, "factory-kb4-exact");
|
|
assert.deepEqual(
|
|
chunk.frames[0].metricObstacles[0].cameraProjection.bboxXyxy,
|
|
[390.5, 280.25, 408.75, 318.5],
|
|
);
|
|
|
|
const overlay = await fetchM4ThreatCameraPointOverlay(replayResultId, 1, {
|
|
endpointRoot,
|
|
fetcher: async (input) => {
|
|
requested = String(input);
|
|
return new Response(JSON.stringify({
|
|
schema_version: "missioncore.m48s-camera-point-overlay/v1",
|
|
result_id: replayResultId,
|
|
sequence: 1,
|
|
source_time_ns: 35_521_857_292,
|
|
points_xyd: [[100.5, 200.25, 3.75], [101.5, 201.25, 3.8]],
|
|
source_frame_count: 11,
|
|
source_point_count: 28000,
|
|
front_point_count: 18000,
|
|
projected_point_count: 12000,
|
|
sample_count: 2,
|
|
window_seconds: 2,
|
|
projection: "factory-kb4-causal-registered-accumulation",
|
|
authority: "visual-derived",
|
|
}), { status: 200 });
|
|
},
|
|
});
|
|
assert.equal(
|
|
requested,
|
|
`${endpointRoot}/${replayResultId}/timeline/frames/1/camera-points`,
|
|
);
|
|
assert.equal(overlay.sourceFrameCount, 11);
|
|
assert.equal(overlay.sampleCount, 2);
|
|
assert.equal(overlay.projection, "factory-kb4-causal-registered-accumulation");
|
|
});
|
|
|
|
test("M4.8R3 turns active low-step components into native fisheye obstacle boxes", () => {
|
|
const obstacle = {
|
|
componentId: "temporal-1855-7",
|
|
state: "current",
|
|
motion: "stationary",
|
|
centroidBodyXyzM: [2.4, 0.1, 0.3],
|
|
cellCentersBodyXyzM: [[2.4, 0.1, 0.3]],
|
|
occupancySource: "additive-low-step",
|
|
cameraProjection: {
|
|
bboxXyxy: [398, 298, 402, 302],
|
|
nearestDepthM: 2.3,
|
|
projectedCellCount: 1,
|
|
projection: "factory-kb4-occupied-voxel-bounds",
|
|
authority: "visual-derived",
|
|
},
|
|
assessment: {
|
|
componentId: "temporal-1855-7",
|
|
decision: "threat",
|
|
corridorIntersection: "intersects",
|
|
relativeSpeedMps: null,
|
|
closestApproachM: 0.2,
|
|
ttcSeconds: null,
|
|
reasonCodes: ["current-corridor-intersection"],
|
|
},
|
|
};
|
|
const boxes = buildM4StaticObstacleBoxes([
|
|
obstacle,
|
|
{ ...obstacle, componentId: "baseline", occupancySource: "baseline" },
|
|
{ ...obstacle, componentId: "held", state: "held" },
|
|
{
|
|
...obstacle,
|
|
componentId: "unknown",
|
|
assessment: {
|
|
...obstacle.assessment,
|
|
decision: "unknown",
|
|
corridorIntersection: "unknown",
|
|
},
|
|
},
|
|
{
|
|
...obstacle,
|
|
componentId: "clear",
|
|
assessment: {
|
|
...obstacle.assessment,
|
|
decision: "not-threat",
|
|
corridorIntersection: "clear",
|
|
},
|
|
},
|
|
], 800, 600);
|
|
assert.equal(boxes.length, 1);
|
|
assert.deepEqual(boxes[0].boxXyxy, [394, 294, 406, 306]);
|
|
assert.equal(boxes[0].label, "OBS #7 · 2,3 м");
|
|
assert.equal(boxes[0].tone, "danger");
|
|
assert.equal(boxes[0].dashed, false);
|
|
});
|
|
|
|
test("M4.8R3 keeps separate CURRENT threats and suppresses their spanning ROLLING box", () => {
|
|
const current = {
|
|
componentId: "temporal-left-1",
|
|
state: "current",
|
|
motion: "stationary",
|
|
centroidBodyXyzM: [3, 0, 0.3],
|
|
cellCentersBodyXyzM: [[3, 0, 0.3]],
|
|
occupancySource: "additive-low-step",
|
|
cameraProjection: {
|
|
bboxXyxy: [350, 260, 410, 310],
|
|
nearestDepthM: 3,
|
|
projectedCellCount: 2,
|
|
projection: "factory-kb4-occupied-voxel-bounds",
|
|
authority: "visual-derived",
|
|
},
|
|
assessment: {
|
|
componentId: "temporal-left-1",
|
|
decision: "threat",
|
|
corridorIntersection: "intersects",
|
|
relativeSpeedMps: null,
|
|
closestApproachM: 2.5,
|
|
ttcSeconds: null,
|
|
reasonCodes: ["current-corridor-intersection"],
|
|
},
|
|
};
|
|
const boxes = buildM4StaticObstacleBoxes([
|
|
current,
|
|
{
|
|
...current,
|
|
componentId: "temporal-right-2",
|
|
cameraProjection: {
|
|
...current.cameraProjection,
|
|
bboxXyxy: [405, 265, 445, 320],
|
|
},
|
|
},
|
|
{
|
|
...current,
|
|
componentId: "rolling-spanning",
|
|
state: "retained",
|
|
cameraProjection: {
|
|
...current.cameraProjection,
|
|
bboxXyxy: [340, 255, 450, 325],
|
|
},
|
|
},
|
|
], 800, 600);
|
|
|
|
assert.equal(boxes.length, 2);
|
|
assert.deepEqual(boxes.map((box) => box.label).sort(), [
|
|
"OBS #1 · 3 м",
|
|
"OBS #2 · 3 м",
|
|
]);
|
|
});
|
|
|
|
test("M4.6 local SLAM surface reprojects registered increments into the active body frame", () => {
|
|
const frames = [
|
|
timelineFrame(0, 10, {
|
|
body_frame: {
|
|
origin_map_xyz_m: [0, 0, 0],
|
|
basis_map_from_body: [[0, -1, 0], [1, 0, 0], [0, 0, 1]],
|
|
},
|
|
point_cloud_body_xyz_m: [[1, 0, 0]],
|
|
}),
|
|
timelineFrame(1, 10.1, {
|
|
body_frame: {
|
|
origin_map_xyz_m: [0, 0, 0],
|
|
basis_map_from_body: [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
|
|
},
|
|
point_cloud_body_xyz_m: [[1, 0, 0]],
|
|
}),
|
|
].map((raw, index) => ({
|
|
sequence: raw.sequence,
|
|
frameId: raw.frame_id,
|
|
sourceTimeNs: raw.source_time_ns,
|
|
sessionSeconds: raw.session_seconds,
|
|
sourceAvailable: true,
|
|
spatialAvailable: true,
|
|
bodyFrame: {
|
|
originMapXyzM: raw.body_frame.origin_map_xyz_m,
|
|
basisMapFromBody: raw.body_frame.basis_map_from_body,
|
|
},
|
|
pointCloudBodyXyzM: raw.point_cloud_body_xyz_m,
|
|
pointCloudSourceCount: 1,
|
|
pointCloudSampleCount: 1,
|
|
pointCloudLayer: "current-increment",
|
|
rollingMapComponentCount: 0,
|
|
metricObstacles: [],
|
|
cameraProposals: [],
|
|
decisionCounts: { threat: 0, "not-threat": 0, unknown: 0 },
|
|
cameraUrl: raw.camera_url,
|
|
}));
|
|
const surface = buildM4LocalSurface(frames, frames[1], {
|
|
windowSeconds: 2,
|
|
voxelSizeM: 0.1,
|
|
radiusM: 12,
|
|
pointLimit: 20_000,
|
|
});
|
|
assert.equal(surface.sourceFrameCount, 2);
|
|
assert.equal(surface.sourcePointCount, 2);
|
|
assert.deepEqual(surface.pointsBodyXyzM, [[0, 1, 0], [1, 0, 0]]);
|
|
});
|
|
|
|
test("M4.6 spatial buffering keeps the active and one future chunk", () => {
|
|
assert.deepEqual(m4ThreatChunkWindowStarts(48, 24, 4489), [48, 72]);
|
|
assert.deepEqual(m4ThreatChunkWindowStarts(0, 24, 4489), [0, 24]);
|
|
});
|
|
|
|
test("M4.6 spatial buffering drops stale in-flight windows across rapid jumps", () => {
|
|
const aborted = [];
|
|
const controller = (start) => ({ abort: () => aborted.push(start) });
|
|
const inFlight = new Map(
|
|
m4ThreatChunkWindowStarts(0, 24, 4489).map((start) => [start, controller(start)]),
|
|
);
|
|
for (const activeStart of [1488, 4488]) {
|
|
const desired = m4ThreatChunkWindowStarts(activeStart, 24, 4489);
|
|
cancelM4ThreatChunkRequestsOutsideWindow(inFlight, desired);
|
|
for (const start of desired) {
|
|
if (!inFlight.has(start)) inFlight.set(start, controller(start));
|
|
}
|
|
}
|
|
assert.deepEqual([...inFlight.keys()], [4488]);
|
|
assert.deepEqual(aborted, [0, 24, 1488, 1512]);
|
|
});
|
|
|
|
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("recorded VIDEO clock cannot reverse an explicit operator pause", () => {
|
|
const range = { startSeconds: 10, endSeconds: 20 };
|
|
assert.deepEqual(
|
|
synchronizeRecordedEvidencePlayback(
|
|
{ currentSeconds: 15, playing: false, rate: 2 },
|
|
{ currentSeconds: 15.1, playing: true, rate: 1 },
|
|
range,
|
|
),
|
|
{ currentSeconds: 15, playing: false, rate: 2 },
|
|
);
|
|
assert.deepEqual(
|
|
synchronizeRecordedEvidencePlayback(
|
|
{ currentSeconds: 10, playing: false, rate: 2 },
|
|
{ currentSeconds: 15.1, playing: true, rate: 1 },
|
|
range,
|
|
),
|
|
{ currentSeconds: 10, playing: false, rate: 2 },
|
|
);
|
|
assert.deepEqual(
|
|
synchronizeRecordedEvidencePlayback(
|
|
{ currentSeconds: 12, playing: true, rate: 2 },
|
|
{ currentSeconds: 15.1, playing: true, rate: 1 },
|
|
range,
|
|
),
|
|
{ currentSeconds: 15.1, playing: true, rate: 2 },
|
|
);
|
|
assert.deepEqual(
|
|
synchronizeRecordedEvidencePlayback(
|
|
{ currentSeconds: 19.9, playing: true, rate: 1 },
|
|
{ currentSeconds: 20, playing: false, rate: 1 },
|
|
range,
|
|
),
|
|
{ currentSeconds: 20, playing: false, rate: 1 },
|
|
);
|
|
});
|
|
|
|
test("M4.6 viewer keeps media and spatial panes on one playback clock", async () => {
|
|
const [visual, visualCss, imageScene, videoScene, pointOverlay, metricScene] = await Promise.all([
|
|
readFile(new URL("../src/workspaces/laboratory/M4ReplayThreatVisual.tsx", import.meta.url), "utf8"),
|
|
readFile(new URL("../src/styles/m4-replay-threat.css", 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/RecordedEvidencePointCloudOverlay.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, /lastSpatialFrameRef/);
|
|
assert.match(visual, /const spatialFrame = frame\?\.spatialAvailable/);
|
|
assert.match(visual, /<ObservationTimeline/);
|
|
assert.match(visual, /useM4ThreatTimelineFrame/);
|
|
assert.match(visual, /resolveObservationSessionReplay\(timeline\.recordedSourceSessionId/);
|
|
assert.match(visual, /label: "VIDEO"/);
|
|
assert.match(visual, /label: "CAMERA"/);
|
|
assert.match(visual, /label: "3D"/);
|
|
assert.match(visual, /label: "PLAN"/);
|
|
assert.match(visual, />\s*POINTS\s*</);
|
|
assert.match(visual, />\s*OBSTACLES\s*</);
|
|
assert.match(visual, /useState\(true\);[\s\S]*setShowStaticObstacles/);
|
|
assert.match(visual, /buildM4StaticObstacleBoxes/);
|
|
assert.match(visual, /pointCloudOverlay=/);
|
|
assert.match(visual, /mediaMode/);
|
|
assert.match(visual, /spatialMode/);
|
|
assert.match(visual, /current === next \? null : next/);
|
|
assert.match(visual, /data-split=\{splitView \? "true" : undefined\}/);
|
|
assert.match(visual, /<SplitPane/);
|
|
assert.match(visual, /primarySize=\{splitView \? splitPrimarySize : mediaMode \? 100 : 0\}/);
|
|
assert.match(visual, /resizable=\{splitView\}/);
|
|
assert.match(visual, /separatorLabel="Изменить размер VIDEO\/CAMERA и 3D\/PLAN"/);
|
|
assert.match(visual, /secondaryMode=\{\{/);
|
|
assert.match(visual, /playback=\{playbackController\.playback\}/);
|
|
assert.match(visual, /clock: mediaMode === "video" \? "external" : "animation"/);
|
|
assert.match(visual, /timelineFrame\.activeSequence \+ 1/);
|
|
assert.match(visual, /segmentCount=\{timeline\.frameCount\}/);
|
|
assert.match(visual, /onPlaybackChange=\{playbackController\.synchronize\}/);
|
|
assert.match(visual, /onPlayingRejected=\{\(\) => playbackController\.setPlaying\(false\)\}/);
|
|
assert.match(visual, /currentSeconds: playbackController\.playback\.currentSeconds/);
|
|
assert.match(visualCss, /m4-replay-threat-visual__deck > \.nodedc-split-pane/);
|
|
assert.match(visualCss, /m4-replay-threat-visual__pane-toolbar\[data-pane-toolbar="media"\]/);
|
|
assert.match(visualCss, /m4-replay-threat-visual__pane-toolbar\[data-pane-toolbar="spatial"\]/);
|
|
assert.match(
|
|
visualCss,
|
|
/data-mode-controls="content"\]:has\([\s\S]*m4-replay-threat-visual__review-controls[\s\S]*data-pane-toolbar="media"\][^{]*\{[^}]*top:\s*3\.8rem/s,
|
|
);
|
|
assert.match(visualCss, /width: 33\.333333%/);
|
|
assert.match(visualCss, /flex-flow: column nowrap/);
|
|
assert.match(visualCss, /m4-replay-threat-visual__overlay > div/);
|
|
assert.match(visualCss, /grid-template-columns: minmax\(0, max-content\)/);
|
|
assert.match(visualCss, /width: max-content/);
|
|
assert.match(visualCss, /--m4-replay-threat-overlay-pane-width/);
|
|
assert.match(visualCss, /white-space: nowrap/);
|
|
assert.match(visualCss, /laboratory-metric-evidence-scene__legend/);
|
|
assert.match(visualCss, /bottom: auto/);
|
|
assert.match(videoScene, /<RecordedFmp4Player/);
|
|
assert.match(imageScene, /<RecordedEvidenceBoxOverlay/);
|
|
assert.match(imageScene, /<RecordedEvidencePointCloudOverlay/);
|
|
assert.match(videoScene, /<RecordedEvidencePointCloudOverlay/);
|
|
assert.match(pointOverlay, /factory-kb4-exact/);
|
|
assert.match(pointOverlay, /factory-kb4-causal-registered-accumulation/);
|
|
assert.match(metricScene, /OrbitControls/);
|
|
assert.match(visual, /LOCAL SLAM/);
|
|
assert.match(visual, /showLocalSurface/);
|
|
assert.match(visual, /pointCloudBodyXyzM=\{spatialFrame\.pointCloudBodyXyzM\}/);
|
|
assert.match(metricScene, /Локальная SLAM-поверхность/);
|
|
assert.match(visual, /showJumpToEnd=\{false\}/);
|
|
assert.doesNotMatch(visual, /Назад на 5 секунд/);
|
|
assert.doesNotMatch(visual, /Вперёд на 5 секунд/);
|
|
assert.doesNotMatch(metricScene, /ЛКМ · вращение/);
|
|
});
|
|
|
|
test("M4.6 keeps the recorded VIDEO player mounted across media mode toggles", async () => {
|
|
const visual = await readFile(
|
|
new URL("../src/workspaces/laboratory/M4ReplayThreatVisual.tsx", import.meta.url),
|
|
"utf8",
|
|
);
|
|
assert.match(visual, /const mediaPane = \(/);
|
|
assert.match(visual, /hidden=\{!mediaMode\}/);
|
|
assert.match(visual, /data-media="video"/);
|
|
assert.match(visual, /hidden=\{mediaMode !== "video"\}/);
|
|
assert.match(visual, /\{videoSource \? \(/);
|
|
assert.doesNotMatch(visual, /\{mediaMode === "video" \? videoSource \? \(/);
|
|
});
|