fix(lab): stabilize replay and densify LiDAR overlay

This commit is contained in:
DCCONSTRUCTIONS
2026-08-25 17:51:50 +03:00
parent 15be698097
commit b5080671c9
8 changed files with 520 additions and 52 deletions
@@ -6,15 +6,20 @@ export interface RecordedEvidencePointCloudOverlayData {
pointsXyd: readonly RecordedEvidenceProjectedPoint[];
sourcePointCount: number;
projectedPointCount: number;
projection: "factory-kb4-exact";
projection:
| "factory-kb4-exact"
| "factory-kb4-causal-registered-accumulation";
ariaLabel: string;
}
const DEPTH_BUCKETS = 64;
function depthColor(depthM: number): string {
function depthBucket(depthM: number): number {
const normalized = Math.max(0, Math.min(1, (depthM - 0.5) / 24));
const bucket = Math.round(normalized * (DEPTH_BUCKETS - 1));
return Math.round(normalized * (DEPTH_BUCKETS - 1));
}
function depthColor(bucket: number): string {
const hue = 18 + bucket / (DEPTH_BUCKETS - 1) * 190;
return `hsla(${hue}, 96%, 62%, 0.86)`;
}
@@ -52,16 +57,26 @@ export function RecordedEvidencePointCloudOverlay({
const offsetX = (width - imageWidth * scale) / 2;
const offsetY = (height - imageHeight * scale) / 2;
const radius = Math.max(0.8, Math.min(2.2, scale * 1.45));
const buckets = Array.from(
{ length: DEPTH_BUCKETS },
(): [number, number][] => [],
);
for (const [imageX, imageY, depthM] of overlay.pointsXyd) {
context.beginPath();
context.arc(
const bucket = depthBucket(depthM);
buckets[bucket]!.push([
offsetX + imageX * scale,
offsetY + imageY * scale,
radius,
0,
Math.PI * 2,
);
context.fillStyle = depthColor(depthM);
]);
}
for (let bucket = 0; bucket < buckets.length; bucket += 1) {
const points = buckets[bucket];
if (!points?.length) continue;
context.beginPath();
for (const [x, y] of points) {
context.moveTo(x + radius, y);
context.arc(x, y, radius, 0, Math.PI * 2);
}
context.fillStyle = depthColor(bucket);
context.fill();
}
};
@@ -170,7 +170,8 @@ export interface M4ThreatTimeline {
pointSampleLimit: number;
maximumSourcePointsPerFrame: number;
pointDelivery: "exact-current-increment";
cameraPointDelivery: "factory-kb4-projected-current-increment" | null;
cameraPointDelivery: "factory-kb4-causal-registered-accumulation" | null;
cameraPointWindowSeconds: number;
cameraPointSampleLimit: number;
worldStateDelivery: "source-paced-latest-wins" | null;
worldStateFrameCount: number;
@@ -189,6 +190,21 @@ export interface M4ThreatTimeline {
corridor: M4ThreatVisualFrame["corridor"];
}
export interface M4ThreatCameraPointOverlay {
resultId: string;
sequence: number;
sourceTimeNs: number;
pointsXyd: readonly (readonly [number, number, number])[];
sourceFrameCount: number;
sourcePointCount: number;
frontPointCount: number;
projectedPointCount: number;
sampleCount: number;
windowSeconds: number;
projection: "factory-kb4-causal-registered-accumulation";
authority: "visual-derived";
}
export interface M4ThreatTimelineChunk {
resultId: string;
startSequence: number;
@@ -651,9 +667,12 @@ export async function fetchM4ThreatTimeline(
? null
: exact(
payload.camera_point_delivery,
"factory-kb4-projected-current-increment",
"factory-kb4-causal-registered-accumulation",
"M4.6 camera point delivery",
),
cameraPointWindowSeconds: payload.camera_point_window_seconds === undefined
? 0
: number(payload.camera_point_window_seconds, "M4.6 camera point window"),
cameraPointSampleLimit: payload.camera_point_sample_limit === undefined
? 0
: integer(payload.camera_point_sample_limit, "M4.6 camera point limit"),
@@ -760,6 +779,62 @@ export async function fetchM4ThreatTimelineChunk(
};
}
export async function fetchM4ThreatCameraPointOverlay(
result: string,
sequence: number,
{
fetcher = fetch,
signal,
endpointRoot = M4_THREAT_TIMELINE_ENDPOINT_ROOT,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
endpointRoot?: string;
} = {},
): Promise<M4ThreatCameraPointOverlay> {
const response = await fetcher(
`${endpointRoot}/${result}/timeline/frames/${sequence}/camera-points`,
{ headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new M4ThreatContractError(`M4.6 camera points: HTTP ${response.status}.`);
}
const payload = object(await response.json(), "M4.6 camera points");
exact(
payload.schema_version,
"missioncore.m48s-camera-point-overlay/v1",
"M4.6 camera point schema",
);
exact(payload.result_id, result, "M4.6 camera point result");
exact(payload.sequence, sequence, "M4.6 camera point sequence");
exact(payload.authority, "visual-derived", "M4.6 camera point authority");
const points = array(payload.points_xyd, "M4.6 accumulated camera points").map(
(point) => vector(point, 3, "M4.6 accumulated camera point") as [number, number, number],
);
const sampleCount = integer(payload.sample_count, "M4.6 camera point sample count");
if (points.length !== sampleCount) {
throw new M4ThreatContractError("M4.6 camera point sample count: нарушен контракт.");
}
return {
resultId: result,
sequence,
sourceTimeNs: integer(payload.source_time_ns, "M4.6 camera point source time"),
pointsXyd: points,
sourceFrameCount: integer(payload.source_frame_count, "M4.6 camera source frames"),
sourcePointCount: integer(payload.source_point_count, "M4.6 camera source points"),
frontPointCount: integer(payload.front_point_count, "M4.6 camera front points"),
projectedPointCount: integer(payload.projected_point_count, "M4.6 camera projected points"),
sampleCount,
windowSeconds: number(payload.window_seconds, "M4.6 camera point window"),
projection: exact(
payload.projection,
"factory-kb4-causal-registered-accumulation",
"M4.6 camera point projection",
),
authority: "visual-derived",
};
}
function parseTimelineFrame(
value: unknown,
result: string,
@@ -42,6 +42,7 @@ import { recordedObservationSources } from "../../core/observation/recordedObser
import { resolveObservationSessionReplay } from "../../core/observation/useObservationSessions";
import type { ObservationSourceDescriptor } from "../../core/runtime/contracts";
import {
useM4ThreatCameraPointOverlay,
useM4ThreatTimelineFrame,
useM4ThreatTimelineMetadata,
} from "./useM4ThreatTimeline";
@@ -224,6 +225,12 @@ export function M4ReplayThreatVisual({
: lastSpatialFrameRef.current?.resultId === resultId
? lastSpatialFrameRef.current.frame
: null;
const cameraPointOverlay = useM4ThreatCameraPointOverlay({
enabled: showMediaPoints,
resultId,
sequence: frame?.sequence ?? null,
endpointRoot: timelineEndpointRoot,
});
const semanticTimeline = useE47SemanticTimelineFrame({
resultId: semantic?.resultId ?? null,
activeSequence: frame?.sequence ?? timelineFrame.activeSequence,
@@ -373,16 +380,27 @@ export function M4ReplayThreatVisual({
ariaLabel: `E47 semantic mask frame ${frame.sequence + 1}`,
}
: undefined;
const accumulatedCameraPoints = cameraPointOverlay.overlay?.sequence === frame?.sequence
? cameraPointOverlay.overlay
: null;
const pointCloudOverlay: RecordedEvidencePointCloudOverlayData | undefined =
showMediaPoints && frame?.cameraProjection === "factory-kb4-exact"
showMediaPoints && accumulatedCameraPoints
? {
pointsXyd: frame.cameraProjectedPointsXyd,
sourcePointCount: frame.cameraProjectedSourceCount,
projectedPointCount: frame.cameraProjectedPointCount,
projection: "factory-kb4-exact",
ariaLabel: `${evidenceLabel} LiDAR projection: ${frame.cameraProjectedSampleCount} points`,
pointsXyd: accumulatedCameraPoints.pointsXyd,
sourcePointCount: accumulatedCameraPoints.sourcePointCount,
projectedPointCount: accumulatedCameraPoints.projectedPointCount,
projection: accumulatedCameraPoints.projection,
ariaLabel: `${evidenceLabel} causal registered LiDAR accumulation: ${accumulatedCameraPoints.sampleCount} points`,
}
: undefined;
: showMediaPoints && frame?.cameraProjection === "factory-kb4-exact"
? {
pointsXyd: frame.cameraProjectedPointsXyd,
sourcePointCount: frame.cameraProjectedSourceCount,
projectedPointCount: frame.cameraProjectedPointCount,
projection: "factory-kb4-exact",
ariaLabel: `${evidenceLabel} current LiDAR projection: ${frame.cameraProjectedSampleCount} points`,
}
: undefined;
const handleMediaModeChange = (next: M4ThreatMediaSelection) => {
if (next === "none") return;
@@ -452,7 +470,7 @@ export function M4ReplayThreatVisual({
shape="pill"
variant={showMediaPoints ? "primary" : "secondary"}
aria-pressed={showMediaPoints}
title="Exact LiDAR increment · factory KB4 camera projection"
title="Actual registered LiDAR · causal accumulation · factory KB4 projection"
onClick={() => setShowMediaPoints((visible) => !visible)}
>
POINTS
@@ -608,9 +626,13 @@ export function M4ReplayThreatVisual({
{frame.worldStateAvailable
? " · world-state delivered"
: ` · world-state gap (${frame.terminalOutcome})`}
{pointCloudOverlay
? ` · camera points ${frame.cameraProjectedSampleCount}/${frame.cameraProjectedPointCount}`
: ""}
{accumulatedCameraPoints
? ` · camera points ${accumulatedCameraPoints.sampleCount}/${accumulatedCameraPoints.projectedPointCount} · causal ${accumulatedCameraPoints.windowSeconds.toFixed(1)} с / ${accumulatedCameraPoints.sourceFrameCount} frames`
: pointCloudOverlay
? ` · camera points ${frame.cameraProjectedSampleCount}/${frame.cameraProjectedPointCount} exact-current · накопление загружается`
: showMediaPoints && cameraPointOverlay.error
? " · накопленное camera cloud недоступно"
: ""}
{semantic && spatialSemanticFrame
? ` · semantic L ${spatialSemanticFrame.counts.labeled} · A ${spatialSemanticFrame.counts.ambiguous} · U ${spatialSemanticFrame.counts.unprojected} · Ø ${spatialSemanticFrame.counts.absent}`
: semantic ? " · semantic buffer" : ""}
@@ -1,17 +1,20 @@
import { useEffect, useMemo, useRef, useState } from "react";
import {
fetchM4ThreatCameraPointOverlay,
fetchM4ThreatTimeline,
fetchM4ThreatTimelineChunk,
selectM4ThreatTimelineSequence,
type M4ThreatCameraPointOverlay,
type M4ThreatTimeline,
type M4ThreatTimelineChunk,
type M4ThreatTimelineFrame,
} from "../../core/laboratory/m4ReplayThreat";
const REQUESTED_CHUNK_FRAMES = 24;
const RETAINED_CHUNK_COUNT = 8;
const PREFETCH_CHUNKS_AHEAD = 2;
const RETAINED_CHUNK_COUNT = 4;
const PREFETCH_CHUNKS_AHEAD = 1;
const RETAINED_CAMERA_POINT_OVERLAYS = 12;
function errorMessage(error: unknown, fallback: string): string {
return error instanceof Error && error.message.trim() ? error.message : fallback;
@@ -24,8 +27,8 @@ export function m4ThreatChunkWindowStarts(
): readonly number[] {
if (chunkSize < 1 || frameCount < 1) return [];
return Array.from(
{ length: PREFETCH_CHUNKS_AHEAD + 2 },
(_, index) => activeChunkStart + (index - 1) * chunkSize,
{ length: PREFETCH_CHUNKS_AHEAD + 1 },
(_, index) => activeChunkStart + index * chunkSize,
).filter((start) => start >= 0 && start < frameCount);
}
@@ -153,8 +156,11 @@ export function useM4ThreatTimelineFrame({
.finally(() => {
if (inFlight.current.get(start) === controller) inFlight.current.delete(start);
});
// Keep server-side JSON work strictly serialized: the active chunk is
// loaded first, then the next chunk is prefetched on the following render.
break;
}
}, [activeChunkStart, chunkSize, endpointRoot, resultId, timeline]);
}, [activeChunkStart, chunkSize, chunks, endpointRoot, resultId, timeline]);
const activeFrame: M4ThreatTimelineFrame | null = useMemo(() => {
if (activeSequence === null || activeChunkStart === null) return null;
@@ -178,3 +184,127 @@ export function useM4ThreatTimelineFrame({
error,
};
}
export function useM4ThreatCameraPointOverlay({
enabled,
resultId,
sequence,
endpointRoot,
}: {
enabled: boolean;
resultId: string;
sequence: number | null;
endpointRoot?: string;
}) {
const [overlay, setOverlay] = useState<M4ThreatCameraPointOverlay | null>(null);
const [error, setError] = useState<string | null>(null);
const desiredRef = useRef<{
resultId: string;
sequence: number;
endpointRoot?: string;
} | null>(null);
const cacheRef = useRef(new Map<string, M4ThreatCameraPointOverlay>());
const runningRef = useRef(false);
const mountedRef = useRef(true);
const pumpRef = useRef<() => void>(() => undefined);
pumpRef.current = () => {
if (runningRef.current || !desiredRef.current) return;
runningRef.current = true;
let settledKey: string | null = null;
void (async () => {
while (mountedRef.current) {
const target = desiredRef.current;
if (!target) break;
const key = `${target.endpointRoot ?? ""}:${target.resultId}:${target.sequence}`;
const cached = cacheRef.current.get(key);
if (cached) {
setOverlay(cached);
setError(null);
settledKey = key;
break;
}
try {
const next = await fetchM4ThreatCameraPointOverlay(
target.resultId,
target.sequence,
{ endpointRoot: target.endpointRoot },
);
cacheRef.current.set(key, next);
while (cacheRef.current.size > RETAINED_CAMERA_POINT_OVERLAYS) {
const oldest = cacheRef.current.keys().next().value as string | undefined;
if (oldest === undefined) break;
cacheRef.current.delete(oldest);
}
const desired = desiredRef.current;
if (
desired
&& desired.resultId === target.resultId
&& desired.sequence === target.sequence
&& desired.endpointRoot === target.endpointRoot
) {
setOverlay(next);
setError(null);
settledKey = key;
break;
}
} catch (caught: unknown) {
const desired = desiredRef.current;
if (
desired
&& desired.resultId === target.resultId
&& desired.sequence === target.sequence
&& desired.endpointRoot === target.endpointRoot
) {
setError(errorMessage(caught, "Накопленное LiDAR-облако камеры недоступно."));
settledKey = key;
break;
}
}
}
})().finally(() => {
runningRef.current = false;
const desired = desiredRef.current;
const desiredKey = desired
? `${desired.endpointRoot ?? ""}:${desired.resultId}:${desired.sequence}`
: null;
if (mountedRef.current && desiredKey && desiredKey !== settledKey) pumpRef.current();
});
};
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
desiredRef.current = null;
};
}, []);
useEffect(() => {
cacheRef.current.clear();
setOverlay(null);
setError(null);
}, [resultId]);
useEffect(() => {
desiredRef.current = enabled && sequence !== null
? { resultId, sequence, endpointRoot }
: null;
if (!desiredRef.current) {
setOverlay(null);
setError(null);
return;
}
const key = `${endpointRoot ?? ""}:${resultId}:${sequence}`;
const cached = cacheRef.current.get(key);
setOverlay(cached ?? null);
if (cached) setError(null);
pumpRef.current();
}, [enabled, endpointRoot, resultId, sequence]);
return {
overlay,
loading: enabled && sequence !== null && overlay?.sequence !== sequence && !error,
error,
};
}
@@ -9,6 +9,7 @@ let fetchM4ThreatReplayResult;
let fetchM4ThreatVisual;
let fetchM4ThreatTimeline;
let fetchM4ThreatTimelineChunk;
let fetchM4ThreatCameraPointOverlay;
let selectM4ThreatTimelineFrame;
let selectM4ThreatTimelineSequence;
let advanceRecordedEvidencePlayback;
@@ -30,6 +31,7 @@ before(async () => {
fetchM4ThreatVisual,
fetchM4ThreatTimeline,
fetchM4ThreatTimelineChunk,
fetchM4ThreatCameraPointOverlay,
selectM4ThreatTimelineFrame,
selectM4ThreatTimelineSequence,
} = await server.ssrLoadModule("/src/core/laboratory/m4ReplayThreat.ts"));
@@ -359,8 +361,9 @@ test("M4.8S timeline binds factory-KB4 camera points through its exact endpoint"
point_sample_limit: 4096,
maximum_source_points_per_frame: 3092,
point_delivery: "exact-current-increment",
camera_point_delivery: "factory-kb4-projected-current-increment",
camera_point_sample_limit: 4096,
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",
world_state_frame_count: 4481,
superseded_frame_count: 8,
@@ -385,7 +388,8 @@ test("M4.8S timeline binds factory-KB4 camera points through its exact endpoint"
},
});
assert.equal(requested, `${endpointRoot}/${replayResultId}/timeline`);
assert.equal(timeline.cameraPointDelivery, "factory-kb4-projected-current-increment");
assert.equal(timeline.cameraPointDelivery, "factory-kb4-causal-registered-accumulation");
assert.equal(timeline.cameraPointWindowSeconds, 2);
assert.equal(timeline.worldStateFrameCount, 4481);
assert.equal(timeline.supersededFrameCount, 8);
@@ -418,6 +422,35 @@ test("M4.8S timeline binds factory-KB4 camera points through its exact endpoint"
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");
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.6 local SLAM surface reprojects registered increments into the active body frame", () => {
@@ -468,9 +501,9 @@ test("M4.6 local SLAM surface reprojects registered increments into the active b
assert.deepEqual(surface.pointsBodyXyzM, [[0, 1, 0], [1, 0, 0]]);
});
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("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", () => {
@@ -486,8 +519,8 @@ test("M4.6 spatial buffering drops stale in-flight windows across rapid jumps",
if (!inFlight.has(start)) inFlight.set(start, controller(start));
}
}
assert.deepEqual([...inFlight.keys()], [4464, 4488]);
assert.deepEqual(aborted, [0, 24, 48, 1464, 1488, 1512, 1536]);
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", () => {
@@ -604,6 +637,7 @@ test("M4.6 viewer keeps media and spatial panes on one playback clock", async ()
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/);
+161 -13
View File
@@ -6,10 +6,12 @@ import copy
import json
import math
import statistics
from bisect import bisect_left
from collections import OrderedDict
from dataclasses import dataclass
from itertools import pairwise
from pathlib import Path
from threading import RLock
from threading import Lock
from typing import Final
import numpy as np
@@ -37,6 +39,14 @@ from .threat_timeline import (
FRAME_EVIDENCE_SCHEMA: Final = "missioncore.m48s-reference-graph-frame-evidence/v0"
EXPECTED_FRAME_COUNT: Final = 4_489
CAMERA_ACCUMULATION_WINDOW_SECONDS: Final = 2.0
CAMERA_ACCUMULATION_POINT_LIMIT: Final = 20_000
CAMERA_POINT_OVERLAY_SCHEMA: Final = "missioncore.m48s-camera-point-overlay/v1"
_FRAME_EVIDENCE_SCHEMA_MARKER: Final = (
b'"schema_version":"missioncore.m48s-reference-graph-frame-evidence/v0"'
)
_SOURCE_ENVELOPE_MARKER: Final = b'"source_envelope":'
_JSON_DECODER: Final = json.JSONDecoder()
class M48sReplayTimelineError(RuntimeError):
@@ -87,7 +97,9 @@ class M48sReplayTimeline:
worker = _object(json.loads(self.worker_path.read_text("utf-8")), "worker result")
self.outcomes = _terminal_outcomes(worker)
self.index = _index_ledger(self.frames_path, self.source_times_ns, self.outcomes)
self._lock = RLock()
self._cache_lock = Lock()
self._chunk_json_cache: OrderedDict[tuple[int, int], bytes] = OrderedDict()
self._camera_point_json_cache: OrderedDict[int, bytes] = OrderedDict()
def metadata(self) -> dict[str, object]:
intervals = [
@@ -114,11 +126,14 @@ class M48sReplayTimeline:
"point_sample_limit": RECORDED_SPATIAL_POINT_LIMIT,
"maximum_source_points_per_frame": self.store.maximum_current_point_count,
"point_delivery": "exact-current-increment",
"camera_point_delivery": "factory-kb4-projected-current-increment",
"camera_point_sample_limit": RECORDED_SPATIAL_POINT_LIMIT,
"camera_point_delivery": "factory-kb4-causal-registered-accumulation",
"camera_point_window_seconds": CAMERA_ACCUMULATION_WINDOW_SECONDS,
"camera_point_sample_limit": CAMERA_ACCUMULATION_POINT_LIMIT,
"world_state_delivery": "source-paced-latest-wins",
"world_state_frame_count": len(self.index.offsets_by_sequence),
"superseded_frame_count": sum(value == "superseded" for value in self.outcomes.values()),
"superseded_frame_count": sum(
value == "superseded" for value in self.outcomes.values()
),
"local_surface_visualization": {
"derivation": "bounded-registered-increment-accumulation",
"window_seconds": RECORDED_LOCAL_SURFACE_WINDOW_SECONDS,
@@ -155,8 +170,7 @@ class M48sReplayTimeline:
if not 1 <= frame_count <= RECORDED_SPATIAL_MAX_CHUNK_FRAMES:
raise M48sReplayTimelineError("M4.8S timeline chunk size is invalid")
stop = min(EXPECTED_FRAME_COUNT, start_sequence + frame_count)
with self._lock:
frames = [self._project_frame(sequence) for sequence in range(start_sequence, stop)]
frames = [self._project_frame(sequence) for sequence in range(start_sequence, stop)]
return {
"schema_version": RECORDED_SPATIAL_CHUNK_SCHEMA,
"result_id": self.result_id,
@@ -169,6 +183,110 @@ class M48sReplayTimeline:
"access": "read-only-bounded-recorded-replay",
}
def chunk_json(self, *, start_sequence: int, frame_count: int) -> bytes:
"""Return one bounded immutable chunk without repeating JSON encoding."""
key = (start_sequence, frame_count)
with self._cache_lock:
cached = self._chunk_json_cache.get(key)
if cached is not None:
self._chunk_json_cache.move_to_end(key)
return cached
content = json.dumps(
self.chunk(start_sequence=start_sequence, frame_count=frame_count),
ensure_ascii=False,
separators=(",", ":"),
).encode("utf-8")
with self._cache_lock:
self._chunk_json_cache[key] = content
self._chunk_json_cache.move_to_end(key)
while len(self._chunk_json_cache) > 12:
self._chunk_json_cache.popitem(last=False)
return content
def camera_point_overlay_json(self, *, sequence: int) -> bytes:
"""Project causal registered LiDAR increments into the current camera.
Every rendered point comes from a sealed map-frame increment at or before
``sequence``. Accumulation is visualization-only: it increases static
surface density but can leave short trails behind moving objects.
"""
if not 0 <= sequence < EXPECTED_FRAME_COUNT:
raise M48sReplayTimelineError("M4.8S camera point sequence is invalid")
with self._cache_lock:
cached = self._camera_point_json_cache.get(sequence)
if cached is not None:
self._camera_point_json_cache.move_to_end(sequence)
return cached
current = self.store.frame_for_index(sequence)
source_time_ns = self.source_times_ns[sequence]
window_ns = round(CAMERA_ACCUMULATION_WINDOW_SECONDS * 1_000_000_000)
first_sequence = bisect_left(self.source_times_ns, source_time_ns - window_ns)
source_frames = []
source_point_count = 0
if current is not None:
for source_sequence in range(first_sequence, sequence + 1):
source = self.store.frame_for_index(source_sequence)
if source is None or source.points_map.size == 0:
continue
source_frames.append(source.points_map)
source_point_count += source.source_point_count
points: list[list[float]] = []
front_point_count = 0
projected_point_count = 0
if current is not None and source_frames:
accumulated = np.concatenate(source_frames, axis=0)
projected = project_map_points_kb4(
accumulated,
position_map_xyz=current.sensor_position_map,
orientation_map_from_lidar_xyzw=current.sensor_orientation_xyzw,
profile=current.projection,
)
front_point_count = projected.camera_front_point_count
projected_point_count = projected.projected_point_count
sample_count = min(projected_point_count, CAMERA_ACCUMULATION_POINT_LIMIT)
indices = np.linspace(
0,
projected_point_count - 1,
num=sample_count,
dtype=np.int64,
)
if indices.size:
xy = projected.pixels_xy[indices]
depth = projected.depths_m[indices, None]
points = np.round(np.concatenate((xy, depth), axis=1), 2).tolist()
payload = {
"schema_version": CAMERA_POINT_OVERLAY_SCHEMA,
"result_id": self.result_id,
"sequence": sequence,
"source_time_ns": source_time_ns,
"points_xyd": points,
"source_frame_count": len(source_frames),
"source_point_count": source_point_count,
"front_point_count": front_point_count,
"projected_point_count": projected_point_count,
"sample_count": len(points),
"window_seconds": CAMERA_ACCUMULATION_WINDOW_SECONDS,
"projection": "factory-kb4-causal-registered-accumulation",
"ground_truth": False,
"authority": "visual-derived",
"access": "read-only-bounded-recorded-replay",
}
content = json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
).encode("utf-8")
with self._cache_lock:
self._camera_point_json_cache[sequence] = content
self._camera_point_json_cache.move_to_end(sequence)
while len(self._camera_point_json_cache) > 32:
self._camera_point_json_cache.popitem(last=False)
return content
def _project_frame(self, sequence: int) -> dict[str, object]:
terminal_outcome = self.outcomes[sequence]
row = self._row(sequence)
@@ -273,7 +391,9 @@ class M48sReplayTimeline:
"semantic_hint": proposal.get("semantic_hint"),
"occupied_support": proposal_id in associated,
"range_m": None,
"threat_decision": None if assessment is None else assessment.get("decision"),
"threat_decision": None
if assessment is None
else assessment.get("decision"),
"threat_reason_codes": []
if assessment is None
else assessment.get("reason_codes"),
@@ -344,10 +464,7 @@ def _index_ledger(
line = stream.readline()
if not line:
break
row = json.loads(line)
if not isinstance(row, dict) or row.get("schema_version") != FRAME_EVIDENCE_SCHEMA:
raise M48sReplayTimelineError("M4.8S ledger schema changed")
envelope = _object(row.get("source_envelope"), "source envelope")
envelope = _ledger_source_envelope(line)
timestamps = _object(envelope.get("timestamps"), "source timestamps")
sequence = envelope.get("sequence")
if (
@@ -366,6 +483,31 @@ def _index_ledger(
return _LedgerIndex(offsets)
def _ledger_source_envelope(line: bytes) -> dict[str, object]:
"""Validate a ledger row while decoding only its small trailing envelope.
The full row can exceed 100 KiB because it contains the delivered world
state. Indexing needs only the sealed top-level schema and source binding;
decoding the complete 473 MiB ledger on every backend start needlessly holds
the GIL for many seconds.
"""
if (
line.count(_FRAME_EVIDENCE_SCHEMA_MARKER) != 1
or line.count(_SOURCE_ENVELOPE_MARKER) != 1
):
raise M48sReplayTimelineError("M4.8S ledger schema changed")
start = line.find(_SOURCE_ENVELOPE_MARKER) + len(_SOURCE_ENVELOPE_MARKER)
try:
tail = line[start:].decode("utf-8")
value, end = _JSON_DECODER.raw_decode(tail)
except (UnicodeDecodeError, json.JSONDecodeError):
raise M48sReplayTimelineError("M4.8S ledger source envelope is invalid") from None
if tail[end:].strip() != "}":
raise M48sReplayTimelineError("M4.8S ledger source envelope moved")
return _object(value, "source envelope")
def _terminal_outcomes(worker: dict[str, object]) -> dict[int, str]:
execution = _object(worker.get("execution"), "execution")
loops = execution.get("loops")
@@ -423,4 +565,10 @@ def _text(value: object, label: str) -> str:
return value
__all__ = ["M48sReplayTimeline", "M48sReplayTimelineError"]
__all__ = [
"CAMERA_ACCUMULATION_POINT_LIMIT",
"CAMERA_ACCUMULATION_WINDOW_SECONDS",
"CAMERA_POINT_OVERLAY_SCHEMA",
"M48sReplayTimeline",
"M48sReplayTimelineError",
]
@@ -159,11 +159,40 @@ def build_m48s_fixed_class_detector_lab_router(
result_id: str,
start: int = Query(default=0, ge=0),
count: int = Query(default=12, ge=1, le=RECORDED_SPATIAL_MAX_CHUNK_FRAMES),
) -> dict[str, object]:
) -> Response:
try:
return timeline(result_id).chunk(start_sequence=start, frame_count=count)
content = timeline(result_id).chunk_json(
start_sequence=start,
frame_count=count,
)
except M48sReplayTimelineError:
raise HTTPException(status_code=404, detail="M4.8S timeline chunk not found") from None
return Response(
content=content,
media_type="application/json",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"X-Content-Type-Options": "nosniff",
},
)
@router.get("/{result_id}/timeline/frames/{sequence}/camera-points")
def get_timeline_camera_points(result_id: str, sequence: int) -> Response:
try:
content = timeline(result_id).camera_point_overlay_json(sequence=sequence)
except M48sReplayTimelineError:
raise HTTPException(
status_code=404,
detail="M4.8S camera point overlay not found",
) from None
return Response(
content=content,
media_type="application/json",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"X-Content-Type-Options": "nosniff",
},
)
@router.get("/{result_id}/timeline/frames/{sequence}/camera")
def get_timeline_camera(result_id: str, sequence: int) -> Response:
@@ -59,8 +59,10 @@ def test_m48s_lab_api_projects_verified_result_frame_and_camera(tmp_path: Path)
assert timeline.json()["superseded_frame_count"] == 8
assert (
timeline.json()["camera_point_delivery"]
== "factory-kb4-projected-current-increment"
== "factory-kb4-causal-registered-accumulation"
)
assert timeline.json()["camera_point_window_seconds"] == 2.0
assert timeline.json()["camera_point_sample_limit"] == 20_000
chunk = client.get(
f"/api/v1/laboratory/m48s/fixed-class-detector/{result_id}/timeline/chunk",
params={"start": 253, "count": 1},
@@ -73,6 +75,19 @@ def test_m48s_lab_api_projects_verified_result_frame_and_camera(tmp_path: Path)
assert any(
item["semantic_hint"] == "dog" for item in replay_frame["camera_proposals"]
)
camera_points = client.get(
f"/api/v1/laboratory/m48s/fixed-class-detector/{result_id}"
"/timeline/frames/253/camera-points"
)
assert camera_points.status_code == 200
camera_point_payload = camera_points.json()
assert camera_point_payload["schema_version"] == "missioncore.m48s-camera-point-overlay/v1"
assert camera_point_payload["projection"] == (
"factory-kb4-causal-registered-accumulation"
)
assert camera_point_payload["source_frame_count"] > 1
assert camera_point_payload["sample_count"] > replay_frame["camera_projected_sample_count"]
assert camera_point_payload["sample_count"] <= 20_000
frame = client.get(f"/api/v1/laboratory/m48s/fixed-class-detector/{result_id}/frames/000253")
assert frame.status_code == 200