fix(perception): stabilize replay body frame
This commit is contained in:
@@ -32,6 +32,15 @@ export interface M4ThreatReplayResult {
|
||||
providerLatencyP95Ms: number;
|
||||
providerLatencyMaxMs: number;
|
||||
};
|
||||
bodyFrame: {
|
||||
available: number;
|
||||
qualified: number;
|
||||
rejected: number;
|
||||
cameraForwardAlignmentDeg: {
|
||||
p95: number;
|
||||
maximum: number;
|
||||
};
|
||||
};
|
||||
reasonCounts: Readonly<Record<string, number>>;
|
||||
};
|
||||
configuration: {
|
||||
@@ -39,6 +48,11 @@ export interface M4ThreatReplayResult {
|
||||
nominalSensorHeightM: number;
|
||||
forwardCorridorM: number;
|
||||
predictionHorizonSeconds: number;
|
||||
bodyFrame: {
|
||||
origin: "local-surface-vertical-projection";
|
||||
up: "vendor-slam-map-gravity-axis";
|
||||
forward: "smoothed-slam-trajectory-validated-by-camera-axis";
|
||||
};
|
||||
};
|
||||
limitations: readonly string[];
|
||||
}
|
||||
@@ -254,7 +268,13 @@ export async function fetchM4ThreatReplayResult({
|
||||
const evidence = object(metrics.evidence, "M4.6 evidence");
|
||||
const fixtures = object(metrics.fixtures, "M4.6 fixtures");
|
||||
const runtime = object(metrics.runtime, "M4.6 runtime");
|
||||
const bodyFrame = object(metrics.body_frame, "M4.6 body frame");
|
||||
const cameraAlignment = object(
|
||||
bodyFrame.camera_forward_alignment_deg,
|
||||
"M4.6 camera alignment",
|
||||
);
|
||||
const configuration = object(item.configuration, "M4.6 configuration");
|
||||
const configuredBodyFrame = object(configuration.body_frame, "M4.6 configured body frame");
|
||||
const sourceResultIds = object(item.source_result_ids, "M4.6 sources");
|
||||
return {
|
||||
resultId: resultId(item.result_id),
|
||||
@@ -290,6 +310,15 @@ export async function fetchM4ThreatReplayResult({
|
||||
providerLatencyP95Ms: number(runtime.provider_latency_p95_ms, "M4.6 p95"),
|
||||
providerLatencyMaxMs: number(runtime.provider_latency_max_ms, "M4.6 max"),
|
||||
},
|
||||
bodyFrame: {
|
||||
available: integer(bodyFrame.available, "M4.6 available body frames"),
|
||||
qualified: integer(bodyFrame.qualified, "M4.6 qualified body frames"),
|
||||
rejected: integer(bodyFrame.rejected, "M4.6 rejected body frames"),
|
||||
cameraForwardAlignmentDeg: {
|
||||
p95: number(cameraAlignment.p95, "M4.6 body frame camera alignment p95"),
|
||||
maximum: number(cameraAlignment.maximum, "M4.6 body frame camera alignment maximum"),
|
||||
},
|
||||
},
|
||||
reasonCounts: Object.fromEntries(
|
||||
Object.entries(object(metrics.reason_counts, "M4.6 reasons")).map(
|
||||
([key, value]) => [key, integer(value, `M4.6 ${key}`)],
|
||||
@@ -301,6 +330,23 @@ export async function fetchM4ThreatReplayResult({
|
||||
nominalSensorHeightM: number(configuration.nominal_sensor_height_m, "M4.6 height"),
|
||||
forwardCorridorM: number(configuration.forward_corridor_m, "M4.6 corridor"),
|
||||
predictionHorizonSeconds: number(configuration.prediction_horizon_seconds, "M4.6 horizon"),
|
||||
bodyFrame: {
|
||||
origin: exact(
|
||||
configuredBodyFrame.origin,
|
||||
"local-surface-vertical-projection",
|
||||
"M4.6 body frame origin",
|
||||
),
|
||||
up: exact(
|
||||
configuredBodyFrame.up,
|
||||
"vendor-slam-map-gravity-axis",
|
||||
"M4.6 body frame up",
|
||||
),
|
||||
forward: exact(
|
||||
configuredBodyFrame.forward,
|
||||
"smoothed-slam-trajectory-validated-by-camera-axis",
|
||||
"M4.6 body frame forward",
|
||||
),
|
||||
},
|
||||
},
|
||||
limitations: array(item.limitations, "M4.6 limitations").map((value) => text(value, "M4.6 limitation")),
|
||||
};
|
||||
|
||||
@@ -41,6 +41,10 @@ export function M4ReplayThreatResultView({
|
||||
label: "Коридор",
|
||||
value: `${result.configuration.forwardCorridorM} м · horizon ${result.configuration.predictionHorizonSeconds} с`,
|
||||
},
|
||||
{
|
||||
label: "Опорная СК",
|
||||
value: `SLAM gravity · route-forward · ${metrics.bodyFrame.qualified}/${metrics.bodyFrame.available} qualified`,
|
||||
},
|
||||
{
|
||||
label: "Визуал",
|
||||
value: "4489-frame VIDEO · 32 exact CAMERA/3D/PLAN samples",
|
||||
@@ -48,14 +52,14 @@ export function M4ReplayThreatResultView({
|
||||
]}
|
||||
brief={{
|
||||
question: "Может ли единый слой обнаруживать потенциальное препятствие по двум независимым источникам, не теряя LiDAR-only объекты и не объявляя camera-only наблюдение безопасным?",
|
||||
approach: "Все 4489 кадров RAVNOVES00 повторно пропущены через неизменяемые detector, metric geometry и temporal ledgers. Geometry-only объекты получают метрическую оценку; camera-only и stale/held остаются unknown. Отдельная матрица из 9 детерминированных сценариев проверяет статические, сближающиеся и расходящиеся случаи.",
|
||||
approach: "Все 4489 кадров RAVNOVES00 повторно пропущены через неизменяемые detector, metric geometry и temporal ledgers. Виртуальный base_footprint привязан к gravity-оси SLAM map и направлению сглаженной траектории, проверенному camera extrinsic. Geometry-only объекты получают метрическую оценку; camera-only и stale/held остаются unknown.",
|
||||
principalResult: `${metrics.evidence.currentMetric.toLocaleString("ru-RU")} current metric и ${metrics.evidence.cameraOnly.toLocaleString("ru-RU")} camera-only наблюдений учтены; ${metrics.reasonCounts["geometry-only-evidence"]?.toLocaleString("ru-RU") ?? "0"} geometry-only оценок не потеряны. Критические fixtures: ${metrics.fixtures.passed}/${metrics.fixtures.total}, ложных safe: ${metrics.fixtures.criticalFalseNotThreat}.`,
|
||||
limitation: "Корпус и коридор пока виртуальные, replay не является live-проходом или физическим collision test. Постоянная скорость — ограниченная модель, а independent object truth остаётся следующим gate.",
|
||||
limitation: "Корпус и коридор пока виртуальные, replay не является live-проходом или физическим collision test. На машине виртуальная привязка должна замениться измеренным rigid T_body_from_sensor; independent object truth остаётся следующим gate.",
|
||||
}}
|
||||
method={{
|
||||
completeness: "complete",
|
||||
executionClass: "hybrid",
|
||||
pipelineId: "dual-evidence-replay-threat/v1",
|
||||
pipelineId: "dual-evidence-replay-threat/v2",
|
||||
components: [
|
||||
{
|
||||
kind: "source",
|
||||
@@ -127,8 +131,8 @@ export function M4ReplayThreatResultView({
|
||||
},
|
||||
]}
|
||||
conclusion={{
|
||||
proved: "На неизменяемом RAVNOVES00 каждый metric, stale/held и camera-only объект получил ровно одну консервативную оценку. Geometry-only препятствия участвуют в threat-решении без класса, camera-only и просроченные данные не превращаются в safe. Видео, точные camera samples и метрическое 3D-доказательство доступны в одном viewer.",
|
||||
notProved: "Не доказаны live realtime, измеренная геометрия физического корпуса, независимая object-level правильность, навигационная или safety-пригодность и выдача команд.",
|
||||
proved: `На неизменяемом RAVNOVES00 каждый metric, stale/held и camera-only объект получил ровно одну консервативную оценку. ${metrics.bodyFrame.qualified}/${metrics.bodyFrame.available} доступных body frames квалифицированы без переноса handheld roll/pitch на SLAM-мир; camera/route alignment p95 ${formatNumber(metrics.bodyFrame.cameraForwardAlignmentDeg.p95, 1)}°. Видео и точное 3D-доказательство доступны в одном viewer.`,
|
||||
notProved: "Не доказаны live realtime, измеренный T_body_from_sensor и геометрия физического корпуса, независимая object-level правильность, навигационная или safety-пригодность и выдача команд.",
|
||||
decision: "Сохранить dual-evidence provider как канонический replay seam и переходить к независимому object-centric gate; физическую геометрию и live/actuation authority не смешивать с дальнейшей CV-разработкой.",
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -54,8 +54,8 @@ test("M4.6 decodes accepted dual-evidence result without physical authority", as
|
||||
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",
|
||||
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)}`,
|
||||
@@ -71,6 +71,12 @@ test("M4.6 decodes accepted dual-evidence result without physical authority", as
|
||||
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: {
|
||||
@@ -78,6 +84,11 @@ test("M4.6 decodes accepted dual-evidence result without physical authority", as
|
||||
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,
|
||||
@@ -90,7 +101,10 @@ test("M4.6 decodes accepted dual-evidence result without physical authority", as
|
||||
assert.equal(result.resultId, resultId);
|
||||
assert.equal(result.metrics.evidence.currentMetric, 27299);
|
||||
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 () => {
|
||||
|
||||
Reference in New Issue
Block a user