5 Commits
29 changed files with 2932 additions and 275 deletions
@@ -169,6 +169,47 @@ function positions(points: readonly LaboratoryMetricPoint3[]): Float32Array {
return result; return result;
} }
function updatePointPositions(
geometry: THREE.BufferGeometry,
points: readonly LaboratoryMetricPoint3[],
): void {
const requiredValues = points.length * 3;
let attribute = geometry.getAttribute("position") as THREE.BufferAttribute | undefined;
if (!attribute || attribute.array.length < requiredValues) {
let capacity = 3;
while (capacity < requiredValues) capacity *= 2;
attribute = new THREE.BufferAttribute(new Float32Array(capacity), 3);
attribute.setUsage(THREE.DynamicDrawUsage);
geometry.setAttribute("position", attribute);
}
const values = attribute.array as Float32Array;
points.forEach((point, index) => {
const [x, y, z] = scenePoint(point);
const offset = index * 3;
values[offset] = x;
values[offset + 1] = y;
values[offset + 2] = z;
});
attribute.needsUpdate = true;
geometry.setDrawRange(0, points.length);
}
function ensurePointColors(
geometry: THREE.BufferGeometry,
pointCount: number,
): THREE.BufferAttribute {
const requiredValues = pointCount * 3;
let attribute = geometry.getAttribute("color") as THREE.BufferAttribute | undefined;
if (!attribute || attribute.array.length < requiredValues) {
let capacity = 3;
while (capacity < requiredValues) capacity *= 2;
attribute = new THREE.BufferAttribute(new Float32Array(capacity), 3);
attribute.setUsage(THREE.DynamicDrawUsage);
geometry.setAttribute("color", attribute);
}
return attribute;
}
function decisionColor( function decisionColor(
host: HTMLElement, host: HTMLElement,
decision: LaboratoryMetricDecision, decision: LaboratoryMetricDecision,
@@ -244,6 +285,12 @@ LaboratoryMetricEvidenceSceneHandle,
const controlsRef = useRef<OrbitControls | null>(null); const controlsRef = useRef<OrbitControls | null>(null);
const staticContentRef = useRef<THREE.Group | null>(null); const staticContentRef = useRef<THREE.Group | null>(null);
const dynamicContentRef = useRef<THREE.Group | null>(null); const dynamicContentRef = useRef<THREE.Group | null>(null);
const classifiedContentRef = useRef<THREE.Group | null>(null);
const localSurfacePointsRef = useRef<THREE.Points | null>(null);
const currentIncrementPointsRef = useRef<THREE.Points | null>(null);
const classifiedMeshesRef = useRef<ReadonlyMap<LaboratoryMetricCellState, THREE.InstancedMesh>>(
new Map(),
);
const [renderError, setRenderError] = useState<string | null>(null); const [renderError, setRenderError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
@@ -278,12 +325,48 @@ LaboratoryMetricEvidenceSceneHandle,
controls.maxDistance = 80; controls.maxDistance = 80;
const staticContent = new THREE.Group(); const staticContent = new THREE.Group();
const dynamicContent = new THREE.Group(); const dynamicContent = new THREE.Group();
scene.add(staticContent, dynamicContent); const classifiedContent = new THREE.Group();
const localSurfacePoints = new THREE.Points(
new THREE.BufferGeometry(),
new THREE.PointsMaterial({
color: tokenColor(host, "--nodedc-accent-rgb", [247, 248, 244]),
size: 1.3,
sizeAttenuation: false,
transparent: true,
opacity: 0.42,
depthWrite: false,
}),
);
const currentIncrementPoints = new THREE.Points(
new THREE.BufferGeometry(),
new THREE.PointsMaterial({
color: tokenColor(host, "--nodedc-text-muted", [147, 151, 159]),
size: 1.55,
sizeAttenuation: false,
transparent: true,
opacity: 0.58,
depthWrite: false,
}),
);
localSurfacePoints.visible = false;
localSurfacePoints.frustumCulled = false;
currentIncrementPoints.visible = false;
currentIncrementPoints.frustumCulled = false;
scene.add(
staticContent,
localSurfacePoints,
currentIncrementPoints,
dynamicContent,
classifiedContent,
);
sceneRef.current = scene; sceneRef.current = scene;
cameraRef.current = camera; cameraRef.current = camera;
controlsRef.current = controls; controlsRef.current = controls;
staticContentRef.current = staticContent; staticContentRef.current = staticContent;
dynamicContentRef.current = dynamicContent; dynamicContentRef.current = dynamicContent;
classifiedContentRef.current = classifiedContent;
localSurfacePointsRef.current = localSurfacePoints;
currentIncrementPointsRef.current = currentIncrementPoints;
const resize = () => { const resize = () => {
const width = Math.max(host.clientWidth, 1); const width = Math.max(host.clientWidth, 1);
@@ -315,6 +398,10 @@ LaboratoryMetricEvidenceSceneHandle,
controlsRef.current = null; controlsRef.current = null;
staticContentRef.current = null; staticContentRef.current = null;
dynamicContentRef.current = null; dynamicContentRef.current = null;
classifiedContentRef.current = null;
localSurfacePointsRef.current = null;
currentIncrementPointsRef.current = null;
classifiedMeshesRef.current = new Map();
}; };
}, []); }, []);
@@ -323,137 +410,71 @@ LaboratoryMetricEvidenceSceneHandle,
if (canvas) canvas.setAttribute("aria-label", label); if (canvas) canvas.setAttribute("aria-label", label);
}, [label]); }, [label]);
useEffect(() => {
const points = localSurfacePointsRef.current;
if (!points) return;
points.visible = showLocalSurface && localSurfaceBodyXyzM.length > 0;
if (points.visible) updatePointPositions(points.geometry, localSurfaceBodyXyzM);
}, [localSurfaceBodyXyzM, showLocalSurface]);
useEffect(() => {
const host = hostRef.current;
const points = currentIncrementPointsRef.current;
if (!host || !points) return;
points.visible = showCurrentIncrement && pointCloudBodyXyzM.length > 0;
if (!points.visible) return;
updatePointPositions(points.geometry, pointCloudBodyXyzM);
const material = points.material as THREE.PointsMaterial;
const hasAlignedSemanticClasses =
pointSemanticClassIds !== undefined
&& pointSemanticClassIds.length === pointCloudBodyXyzM.length
&& semanticClasses !== undefined
&& semanticPalette !== undefined;
if (!hasAlignedSemanticClasses) {
if (material.vertexColors) {
material.vertexColors = false;
material.needsUpdate = true;
}
material.color.copy(tokenColor(host, "--nodedc-text-muted", [147, 151, 159]));
return;
}
const declaredIds = new Set(semanticClasses.map((item) => item.id));
const colorsByClassId = new Map<number, readonly [number, number, number]>();
for (const entry of semanticPalette) {
if (!declaredIds.has(entry.classId)) continue;
const rgb = resolveRecordedEvidenceSemanticRgb(host, entry.color);
if (rgb) colorsByClassId.set(entry.classId, rgb);
}
const context = tokenColor(host, "--nodedc-text-muted", [147, 151, 159]);
const colorAttribute = ensurePointColors(points.geometry, pointCloudBodyXyzM.length);
const pointColors = colorAttribute.array as Float32Array;
pointSemanticClassIds.forEach((classId, index) => {
const rgb = classId === null ? undefined : colorsByClassId.get(classId);
const offset = index * 3;
pointColors[offset] = rgb ? rgb[0] / 255 : context.r;
pointColors[offset + 1] = rgb ? rgb[1] / 255 : context.g;
pointColors[offset + 2] = rgb ? rgb[2] / 255 : context.b;
});
colorAttribute.needsUpdate = true;
if (!material.vertexColors) {
material.vertexColors = true;
material.needsUpdate = true;
}
material.color.setRGB(1, 1, 1);
}, [
pointCloudBodyXyzM,
pointSemanticClassIds,
semanticClasses,
semanticPalette,
showCurrentIncrement,
]);
useEffect(() => { useEffect(() => {
const host = hostRef.current; const host = hostRef.current;
const content = dynamicContentRef.current; const content = dynamicContentRef.current;
if (!host || !content) return; if (!host || !content) return;
clearGroup(content); clearGroup(content);
if (showLocalSurface) {
const localSurfaceGeometry = new THREE.BufferGeometry();
localSurfaceGeometry.setAttribute(
"position",
new THREE.BufferAttribute(positions(localSurfaceBodyXyzM), 3),
);
content.add(new THREE.Points(
localSurfaceGeometry,
new THREE.PointsMaterial({
color: tokenColor(host, "--nodedc-accent-rgb", [247, 248, 244]),
size: 1.3,
sizeAttenuation: false,
transparent: true,
opacity: 0.42,
depthWrite: false,
}),
));
}
if (showCurrentIncrement) {
const contextGeometry = new THREE.BufferGeometry();
contextGeometry.setAttribute(
"position",
new THREE.BufferAttribute(positions(pointCloudBodyXyzM), 3),
);
const hasAlignedSemanticClasses =
pointSemanticClassIds !== undefined
&& pointSemanticClassIds.length === pointCloudBodyXyzM.length
&& semanticClasses !== undefined
&& semanticPalette !== undefined;
if (hasAlignedSemanticClasses) {
const declaredIds = new Set(semanticClasses.map((item) => item.id));
const colorsByClassId = new Map<number, readonly [number, number, number]>();
for (const entry of semanticPalette) {
if (!declaredIds.has(entry.classId)) continue;
const rgb = resolveRecordedEvidenceSemanticRgb(host, entry.color);
if (rgb) colorsByClassId.set(entry.classId, rgb);
}
const context = tokenColor(host, "--nodedc-text-muted", [147, 151, 159]);
const pointColors = new Float32Array(pointCloudBodyXyzM.length * 3);
pointSemanticClassIds.forEach((classId, index) => {
const rgb = classId === null ? undefined : colorsByClassId.get(classId);
const offset = index * 3;
pointColors[offset] = rgb ? rgb[0] / 255 : context.r;
pointColors[offset + 1] = rgb ? rgb[1] / 255 : context.g;
pointColors[offset + 2] = rgb ? rgb[2] / 255 : context.b;
});
contextGeometry.setAttribute(
"color",
new THREE.BufferAttribute(pointColors, 3),
);
}
content.add(new THREE.Points(
contextGeometry,
new THREE.PointsMaterial({
color: hasAlignedSemanticClasses
? new THREE.Color(1, 1, 1)
: tokenColor(host, "--nodedc-text-muted", [147, 151, 159]),
vertexColors: hasAlignedSemanticClasses,
size: 1.55,
sizeAttenuation: false,
transparent: true,
opacity: 0.58,
depthWrite: false,
}),
));
}
if (showClassifiedCells && classifiedCells.length) {
const cellsByState = new Map<LaboratoryMetricCellState, LaboratoryMetricCellEvidence[]>();
for (const cell of classifiedCells) {
const cells = cellsByState.get(cell.state) ?? [];
cells.push(cell);
cellsByState.set(cell.state, cells);
}
for (const [state, cells] of cellsByState) {
const color = state === "ground-support"
? tokenColor(host, "--nodedc-success-rgb", [181, 255, 90])
: state === "nonground-occupied"
? tokenColor(host, "--nodedc-danger-rgb", [255, 104, 112])
: state === "unknown-rejected"
? tokenColor(host, "--nodedc-warning-rgb", [255, 197, 92])
: tokenColor(host, "--nodedc-text-muted", [96, 99, 106]);
const geometry = new THREE.BoxGeometry(
classifiedCellSizeM * 0.92,
1,
classifiedCellSizeM * 0.92,
);
const material = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: state === "unobserved" ? 0.035 : state === "ground-support" ? 0.12 : 0.24,
depthWrite: false,
});
const mesh = new THREE.InstancedMesh(geometry, material, cells.length);
const matrix = new THREE.Matrix4();
const scale = new THREE.Vector3(1, 1, 1);
const rotation = new THREE.Quaternion();
cells.forEach((cell, index) => {
const minimum = cell.zBoundsM[0];
const maximum = cell.zBoundsM[1];
const height = minimum === null || maximum === null
? 0.018
: Math.max(0.018, maximum - minimum);
const centerZ = minimum === null || maximum === null
? -0.012
: (minimum + maximum) / 2;
const [sceneX, sceneY, sceneZ] = scenePoint([
cell.centerBodyXyM[0],
cell.centerBodyXyM[1],
centerZ,
]);
scale.set(1, height, 1);
matrix.compose(
new THREE.Vector3(sceneX, sceneY, sceneZ),
rotation,
scale,
);
mesh.setMatrixAt(index, matrix);
});
mesh.instanceMatrix.needsUpdate = true;
content.add(mesh);
}
}
for (const obstacle of obstacles) { for (const obstacle of obstacles) {
if ( if (
( (
@@ -526,20 +547,97 @@ LaboratoryMetricEvidenceSceneHandle,
}, [ }, [
obstacles, obstacles,
occupiedVoxelSizeM, occupiedVoxelSizeM,
localSurfaceBodyXyzM,
pointCloudBodyXyzM,
pointSemanticClassIds,
semanticClasses,
semanticPalette,
classifiedCells,
classifiedCellSizeM,
showCurrentIncrement, showCurrentIncrement,
showClassifiedCells,
showLocalSurface,
showRollingMap, showRollingMap,
showLowStep, showLowStep,
]); ]);
useEffect(() => {
const host = hostRef.current;
const content = classifiedContentRef.current;
if (!host || !content) return;
content.visible = showClassifiedCells && classifiedCells.length > 0;
if (!content.visible) return;
const requiredCapacity = classifiedCells.length;
const currentMeshes = classifiedMeshesRef.current;
const firstMesh = currentMeshes.values().next().value as THREE.InstancedMesh | undefined;
const needsAllocation = !firstMesh
|| Number(firstMesh.userData.capacity ?? 0) < requiredCapacity
|| Number(firstMesh.userData.cellSizeM ?? 0) !== classifiedCellSizeM;
if (needsAllocation) {
clearGroup(content);
const meshes = new Map<LaboratoryMetricCellState, THREE.InstancedMesh>();
const states: readonly LaboratoryMetricCellState[] = [
"unobserved",
"ground-support",
"nonground-occupied",
"unknown-rejected",
];
for (const state of states) {
const color = state === "ground-support"
? tokenColor(host, "--nodedc-success-rgb", [181, 255, 90])
: state === "nonground-occupied"
? tokenColor(host, "--nodedc-danger-rgb", [255, 104, 112])
: state === "unknown-rejected"
? tokenColor(host, "--nodedc-warning-rgb", [255, 197, 92])
: tokenColor(host, "--nodedc-text-muted", [96, 99, 106]);
const mesh = new THREE.InstancedMesh(
new THREE.BoxGeometry(classifiedCellSizeM * 0.92, 1, classifiedCellSizeM * 0.92),
new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: state === "unobserved" ? 0.035 : state === "ground-support" ? 0.12 : 0.24,
depthWrite: false,
}),
requiredCapacity,
);
mesh.count = 0;
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
mesh.frustumCulled = false;
mesh.userData.capacity = requiredCapacity;
mesh.userData.cellSizeM = classifiedCellSizeM;
meshes.set(state, mesh);
content.add(mesh);
}
classifiedMeshesRef.current = meshes;
}
const meshes = classifiedMeshesRef.current;
const counts = new Map<LaboratoryMetricCellState, number>();
const matrix = new THREE.Matrix4();
const position = new THREE.Vector3();
const scale = new THREE.Vector3(1, 1, 1);
const rotation = new THREE.Quaternion();
for (const cell of classifiedCells) {
const mesh = meshes.get(cell.state);
if (!mesh) continue;
const index = counts.get(cell.state) ?? 0;
const minimum = cell.zBoundsM[0];
const maximum = cell.zBoundsM[1];
const height = minimum === null || maximum === null
? 0.018
: Math.max(0.018, maximum - minimum);
const centerZ = minimum === null || maximum === null
? -0.012
: (minimum + maximum) / 2;
const [sceneX, sceneY, sceneZ] = scenePoint([
cell.centerBodyXyM[0],
cell.centerBodyXyM[1],
centerZ,
]);
position.set(sceneX, sceneY, sceneZ);
scale.set(1, height, 1);
matrix.compose(position, rotation, scale);
mesh.setMatrixAt(index, matrix);
counts.set(cell.state, index + 1);
}
for (const [state, mesh] of meshes) {
mesh.count = counts.get(state) ?? 0;
mesh.instanceMatrix.needsUpdate = true;
}
}, [classifiedCellSizeM, classifiedCells, showClassifiedCells]);
useEffect(() => { useEffect(() => {
const host = hostRef.current; const host = hostRef.current;
const content = staticContentRef.current; const content = staticContentRef.current;
@@ -510,16 +510,21 @@ export function RecordedEvidenceSemanticMaskOverlay({
}) { }) {
const canvasRef = useRef<HTMLCanvasElement | null>(null); const canvasRef = useRef<HTMLCanvasElement | null>(null);
const rendererRef = useRef<SemanticMaskRenderer | null | undefined>(undefined); const rendererRef = useRef<SemanticMaskRenderer | null | undefined>(undefined);
const lastReadyMaskRef = useRef<{ series: string; mask: DecodedSemanticMask } | null>(null);
const [rendererMode, setRendererMode] = useState<"webgl" | "2d">("webgl"); const [rendererMode, setRendererMode] = useState<"webgl" | "2d">("webgl");
const [mask, setMask] = useState<DecodedSemanticMask | null>(null); const [mask, setMask] = useState<DecodedSemanticMask | null>(null);
const [failure, setFailure] = useState<string | null>(null); const [failure, setFailure] = useState<string | null>(null);
const expectedKey = semanticMaskKey(src, imageWidth, imageHeight); const expectedKey = semanticMaskKey(src, imageWidth, imageHeight);
const prefetchSignature = prefetchSrcs.join("\n"); const prefetchSignature = prefetchSrcs.join("\n");
const renderMask = failure const series = src.slice(0, Math.max(src.lastIndexOf("/"), 0));
if (lastReadyMaskRef.current?.series !== series) lastReadyMaskRef.current = null;
const exactMask = failure
? null ? null
: mask?.key === expectedKey : mask?.key === expectedKey
? mask ? mask
: decodedMaskCache.get(expectedKey) ?? null; : decodedMaskCache.get(expectedKey) ?? null;
if (exactMask) lastReadyMaskRef.current = { series, mask: exactMask };
const renderMask = exactMask ?? lastReadyMaskRef.current?.mask ?? null;
useEffect(() => { useEffect(() => {
setFailure(null); setFailure(null);
@@ -627,8 +632,8 @@ export function RecordedEvidenceSemanticMaskOverlay({
className="recorded-evidence-semantic-mask-overlay" className="recorded-evidence-semantic-mask-overlay"
role="img" role="img"
aria-label={ariaLabel} aria-label={ariaLabel}
aria-busy={!failure && !renderMask} aria-busy={!failure && !exactMask}
data-state={failure ? "error" : renderMask ? "ready" : "loading"} data-state={failure ? "error" : exactMask ? "ready" : renderMask ? "stale" : "loading"}
data-renderer={rendererMode} data-renderer={rendererMode}
style={{ zIndex: 1 }} style={{ zIndex: 1 }}
/> />
@@ -84,6 +84,48 @@ export interface M49TgsFullShadowSpatialChunk {
frames: readonly M49TgsFullShadowSpatial[]; frames: readonly M49TgsFullShadowSpatial[];
} }
export type M49TgsFullShadowPlaybackTrackId =
| "frames"
| "centers"
| "states"
| "z-bounds";
export interface M49TgsFullShadowPlaybackProgress {
phase: "manifest" | "download" | "verify" | "ready";
trackId: M49TgsFullShadowPlaybackTrackId | null;
loadedBytes: number;
totalBytes: number;
}
interface M49TgsFullShadowPlaybackTrack {
id: M49TgsFullShadowPlaybackTrackId;
url: string;
mediaType: string;
dtype: "ndjson" | "<f4" | "|u1";
shape: readonly number[];
byteLength: number;
sha256: string;
}
export interface M49TgsFullShadowPlaybackFrame {
sourceSequence: number;
sourceFrameIndex: number;
sessionSeconds: number;
sampleAvailable: boolean;
metrics: M49TgsFullShadowSpatial["metrics"];
}
export interface M49TgsFullShadowPlaybackPack {
resultId: string;
frameCount: 4489;
cellCount: 2244;
totalByteLength: number;
centersXyM: readonly (readonly [number, number])[];
states: Uint8Array;
zBoundsM: Float32Array;
frames: readonly M49TgsFullShadowPlaybackFrame[];
}
export class M49TgsFullShadowContractError extends Error {} export class M49TgsFullShadowContractError extends Error {}
function objectValue(value: unknown, label: string): Record<string, unknown> { function objectValue(value: unknown, label: string): Record<string, unknown> {
@@ -335,3 +377,285 @@ export async function fetchM49TgsFullShadowSpatialChunk(
)), )),
}; };
} }
function playbackTrackId(value: unknown, label: string): M49TgsFullShadowPlaybackTrackId {
const parsed = text(value, label);
if (parsed !== "frames" && parsed !== "centers" && parsed !== "states" && parsed !== "z-bounds") {
throw new M49TgsFullShadowContractError(`${label}: неизвестная playback-дорожка.`);
}
return parsed;
}
function playbackDtype(value: unknown, label: string): M49TgsFullShadowPlaybackTrack["dtype"] {
const parsed = text(value, label);
if (parsed !== "ndjson" && parsed !== "<f4" && parsed !== "|u1") {
throw new M49TgsFullShadowContractError(`${label}: неизвестный dtype.`);
}
return parsed;
}
function playbackShape(value: unknown, label: string): readonly number[] {
if (!Array.isArray(value) || !value.length) {
throw new M49TgsFullShadowContractError(`${label}: ожидалась размерность.`);
}
return value.map((item, index) => integer(item, `${label}.${index}`));
}
async function fetchPlaybackManifest(
id: string,
fetcher: LaboratoryFetch,
signal: AbortSignal | undefined,
): Promise<{
frameCount: 4489;
cellCount: 2244;
totalByteLength: number;
tracks: ReadonlyMap<M49TgsFullShadowPlaybackTrackId, M49TgsFullShadowPlaybackTrack>;
}> {
const response = await fetcher(
`/api/v1/laboratory/m49/tgs-full-shadow/${encodeURIComponent(id)}/playback/manifest`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new M49TgsFullShadowContractError(`M49 playback manifest недоступен: HTTP ${response.status}.`);
}
const payload = objectValue(await response.json(), "M49 playback manifest");
exact(payload.schema_version, "missioncore.m49-tgs-full-shadow-playback/v1", "M49 playback schema");
exact(payload.result_id, id, "M49 playback result");
exact(payload.coordinate_frame, "map-gravity-local", "M49 playback frame");
exact(payload.access, "read-only", "M49 playback access");
const frameCount = exact(integer(payload.frame_count, "M49 playback frames"), 4489, "M49 playback frames");
const cellCount = exact(integer(payload.cell_count, "M49 playback cells"), 2244, "M49 playback cells");
const totalByteLength = integer(payload.total_byte_length, "M49 playback bytes");
if (!Array.isArray(payload.tracks) || payload.tracks.length !== 4) {
throw new M49TgsFullShadowContractError("M49 playback tracks: неверная размерность.");
}
const tracks = new Map<M49TgsFullShadowPlaybackTrackId, M49TgsFullShadowPlaybackTrack>();
for (const [index, value] of payload.tracks.entries()) {
const row = objectValue(value, `M49 playback track ${index}`);
const trackId = playbackTrackId(row.id, `M49 playback track ${index}.id`);
const sha256 = text(row.sha256, `M49 playback track ${trackId}.sha256`);
if (!/^[a-f0-9]{64}$/.test(sha256) || tracks.has(trackId)) {
throw new M49TgsFullShadowContractError(`M49 playback track ${trackId}: нарушена идентичность.`);
}
tracks.set(trackId, {
id: trackId,
url: text(row.url, `M49 playback track ${trackId}.url`),
mediaType: text(row.media_type, `M49 playback track ${trackId}.media_type`),
dtype: playbackDtype(row.dtype, `M49 playback track ${trackId}.dtype`),
shape: playbackShape(row.shape, `M49 playback track ${trackId}.shape`),
byteLength: integer(row.byte_length, `M49 playback track ${trackId}.byte_length`),
sha256,
});
}
if ([...tracks.values()].reduce((sum, track) => sum + track.byteLength, 0) !== totalByteLength) {
throw new M49TgsFullShadowContractError("M49 playback bytes: нарушен accounting.");
}
return { frameCount, cellCount, totalByteLength, tracks };
}
function sha256Hex(buffer: ArrayBuffer): Promise<string> {
if (!globalThis.crypto?.subtle) {
throw new M49TgsFullShadowContractError("Браузер не поддерживает проверку playback SHA-256.");
}
return globalThis.crypto.subtle.digest("SHA-256", buffer).then((digest) => (
[...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("")
));
}
async function fetchPlaybackTrack(
track: M49TgsFullShadowPlaybackTrack,
fetcher: LaboratoryFetch,
signal: AbortSignal | undefined,
completedBytes: number,
totalBytes: number,
onProgress: ((progress: M49TgsFullShadowPlaybackProgress) => void) | undefined,
): Promise<ArrayBuffer> {
const response = await fetcher(track.url, {
method: "GET",
headers: { Accept: track.mediaType },
signal,
});
if (!response.ok) {
throw new M49TgsFullShadowContractError(`M49 playback ${track.id} недоступен: HTTP ${response.status}.`);
}
const bytes = new Uint8Array(track.byteLength);
if (response.body) {
const reader = response.body.getReader();
let offset = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (offset + value.byteLength > bytes.byteLength) {
throw new M49TgsFullShadowContractError(`M49 playback ${track.id}: превышен объявленный размер.`);
}
bytes.set(value, offset);
offset += value.byteLength;
onProgress?.({
phase: "download",
trackId: track.id,
loadedBytes: completedBytes + offset,
totalBytes,
});
}
if (offset !== bytes.byteLength) {
throw new M49TgsFullShadowContractError(`M49 playback ${track.id}: получен неполный файл.`);
}
} else {
const fallback = new Uint8Array(await response.arrayBuffer());
if (fallback.byteLength !== bytes.byteLength) {
throw new M49TgsFullShadowContractError(`M49 playback ${track.id}: получен неверный размер.`);
}
bytes.set(fallback);
}
onProgress?.({
phase: "verify",
trackId: track.id,
loadedBytes: completedBytes + bytes.byteLength,
totalBytes,
});
if (await sha256Hex(bytes.buffer) !== track.sha256) {
throw new M49TgsFullShadowContractError(`M49 playback ${track.id}: SHA-256 не совпал.`);
}
return bytes.buffer;
}
function equalShape(actual: readonly number[], expected: readonly number[]): boolean {
return actual.length === expected.length && actual.every((value, index) => value === expected[index]);
}
export function parseM49TgsNpyTrack(
buffer: ArrayBuffer,
expectedDtype: "<f4" | "|u1",
expectedShape: readonly number[],
): Float32Array | Uint8Array {
const bytes = new Uint8Array(buffer);
if (bytes.byteLength < 12
|| bytes[0] !== 0x93
|| String.fromCharCode(...bytes.subarray(1, 6)) !== "NUMPY") {
throw new M49TgsFullShadowContractError("M49 playback NPY: неверная сигнатура.");
}
const major = bytes[6];
const headerLength = major === 1
? new DataView(buffer).getUint16(8, true)
: major === 2 || major === 3
? new DataView(buffer).getUint32(8, true)
: -1;
const headerOffset = major === 1 ? 10 : 12;
if (headerLength < 0 || headerOffset + headerLength > bytes.byteLength) {
throw new M49TgsFullShadowContractError("M49 playback NPY: неверный заголовок.");
}
const header = new TextDecoder("latin1").decode(bytes.subarray(headerOffset, headerOffset + headerLength));
const dtype = header.match(/["']descr["']\s*:\s*["']([^"']+)["']/)?.[1];
const fortran = header.match(/["']fortran_order["']\s*:\s*(True|False)/)?.[1];
const rawShape = header.match(/["']shape["']\s*:\s*\(([^)]*)\)/)?.[1];
const shape = rawShape?.split(",").map((value) => value.trim()).filter(Boolean).map(Number) ?? [];
if (dtype !== expectedDtype || fortran !== "False" || !equalShape(shape, expectedShape)) {
throw new M49TgsFullShadowContractError("M49 playback NPY: dtype или shape изменились.");
}
const count = expectedShape.reduce((product, value) => product * value, 1);
const dataOffset = headerOffset + headerLength;
const bytesPerValue = expectedDtype === "<f4" ? 4 : 1;
if (dataOffset + count * bytesPerValue !== buffer.byteLength) {
throw new M49TgsFullShadowContractError("M49 playback NPY: длина payload изменилась.");
}
return expectedDtype === "<f4"
? new Float32Array(buffer, dataOffset, count)
: new Uint8Array(buffer, dataOffset, count);
}
function parsePlaybackFrames(buffer: ArrayBuffer): readonly M49TgsFullShadowPlaybackFrame[] {
const lines = new TextDecoder().decode(buffer).trim().split("\n");
if (lines.length !== 4489) {
throw new M49TgsFullShadowContractError("M49 playback frames: неверная размерность.");
}
return lines.map((line, sourceSequence) => {
const row = objectValue(JSON.parse(line), `M49 playback frame ${sourceSequence}`);
return {
sourceSequence,
sourceFrameIndex: integer(row.source_frame_index, `M49 playback frame ${sourceSequence}.source`),
sessionSeconds: numberValue(row.session_seconds, `M49 playback frame ${sourceSequence}.time`),
sampleAvailable: booleanValue(row.sample_available, `M49 playback frame ${sourceSequence}.available`),
metrics: {
eligiblePointCount: integer(row.eligible_point_count, `M49 playback frame ${sourceSequence}.eligible`),
groundPointCount: integer(row.ground_point_count, `M49 playback frame ${sourceSequence}.ground`),
nongroundPointCount: integer(row.nonground_point_count, `M49 playback frame ${sourceSequence}.nonground`),
rejectedPointCount: integer(row.rejected_point_count, `M49 playback frame ${sourceSequence}.rejected`),
occupiedCellCount: integer(row.occupied_cell_count, `M49 playback frame ${sourceSequence}.occupied`),
},
};
});
}
export async function fetchM49TgsFullShadowPlaybackPack(
id: string,
{
fetcher = fetch,
signal,
onProgress,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
onProgress?: (progress: M49TgsFullShadowPlaybackProgress) => void;
} = {},
): Promise<M49TgsFullShadowPlaybackPack> {
if (!RESULT_ID.test(id)) {
throw new M49TgsFullShadowContractError("M49 playback identity недопустима.");
}
onProgress?.({ phase: "manifest", trackId: null, loadedBytes: 0, totalBytes: 0 });
const manifest = await fetchPlaybackManifest(id, fetcher, signal);
const buffers = new Map<M49TgsFullShadowPlaybackTrackId, ArrayBuffer>();
let completedBytes = 0;
const order: readonly M49TgsFullShadowPlaybackTrackId[] = ["frames", "centers", "states", "z-bounds"];
for (const trackId of order) {
const track = manifest.tracks.get(trackId);
if (!track) throw new M49TgsFullShadowContractError(`M49 playback ${trackId}: дорожка отсутствует.`);
const buffer = await fetchPlaybackTrack(
track,
fetcher,
signal,
completedBytes,
manifest.totalByteLength,
onProgress,
);
buffers.set(trackId, buffer);
completedBytes += track.byteLength;
}
const centersTrack = manifest.tracks.get("centers")!;
const statesTrack = manifest.tracks.get("states")!;
const zBoundsTrack = manifest.tracks.get("z-bounds")!;
const centersRaw = parseM49TgsNpyTrack(
buffers.get("centers")!,
"<f4",
centersTrack.shape,
) as Float32Array;
const centersXyM = Array.from({ length: manifest.cellCount }, (_, index) => (
[centersRaw[index * 2]!, centersRaw[index * 2 + 1]!] as const
));
const states = parseM49TgsNpyTrack(
buffers.get("states")!,
"|u1",
statesTrack.shape,
) as Uint8Array;
const zBoundsM = parseM49TgsNpyTrack(
buffers.get("z-bounds")!,
"<f4",
zBoundsTrack.shape,
) as Float32Array;
const frames = parsePlaybackFrames(buffers.get("frames")!);
onProgress?.({
phase: "ready",
trackId: null,
loadedBytes: manifest.totalByteLength,
totalBytes: manifest.totalByteLength,
});
return {
resultId: id,
frameCount: manifest.frameCount,
cellCount: manifest.cellCount,
totalByteLength: manifest.totalByteLength,
centersXyM,
states,
zBoundsM,
frames,
};
}
@@ -226,6 +226,20 @@ export interface M4ThreatTimelineChunk {
frames: readonly M4ThreatTimelineFrame[]; frames: readonly M4ThreatTimelineFrame[];
} }
export interface M4ThreatPlaybackProgress {
phase: "manifest" | "download" | "verify" | "ready";
loadedBytes: number;
totalBytes: number;
}
export interface M4ThreatPlaybackPointPack {
resultId: string;
frameCount: 4489;
pointCount: number;
pointOffsets: Uint32Array;
pointsMapXyzM: Float32Array;
}
type LaboratoryFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>; type LaboratoryFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
export const M4_THREAT_TIMELINE_ENDPOINT_ROOT = "/api/v1/laboratory/m4-threat/results"; export const M4_THREAT_TIMELINE_ENDPOINT_ROOT = "/api/v1/laboratory/m4-threat/results";
class M4ThreatContractError extends Error {} class M4ThreatContractError extends Error {}
@@ -803,11 +817,13 @@ export async function fetchM4ThreatTimelineChunk(
signal, signal,
endpointRoot = M4_THREAT_TIMELINE_ENDPOINT_ROOT, endpointRoot = M4_THREAT_TIMELINE_ENDPOINT_ROOT,
cameraObstacleProjectionDelivery = null, cameraObstacleProjectionDelivery = null,
playbackPointPack,
}: { }: {
fetcher?: LaboratoryFetch; fetcher?: LaboratoryFetch;
signal?: AbortSignal; signal?: AbortSignal;
endpointRoot?: string; endpointRoot?: string;
cameraObstacleProjectionDelivery?: M4ThreatTimeline["cameraObstacleProjectionDelivery"]; cameraObstacleProjectionDelivery?: M4ThreatTimeline["cameraObstacleProjectionDelivery"];
playbackPointPack?: M4ThreatPlaybackPointPack;
} = {}, } = {},
): Promise<M4ThreatTimelineChunk> { ): Promise<M4ThreatTimelineChunk> {
const params = new URLSearchParams({ const params = new URLSearchParams({
@@ -817,6 +833,7 @@ export async function fetchM4ThreatTimelineChunk(
if (cameraObstacleProjectionDelivery !== null) { if (cameraObstacleProjectionDelivery !== null) {
params.set("obstacle_projection", cameraObstacleProjectionDelivery); params.set("obstacle_projection", cameraObstacleProjectionDelivery);
} }
if (playbackPointPack) params.set("include_points", "false");
const response = await fetcher( const response = await fetcher(
`${endpointRoot}/${result}/timeline/chunk?${params}`, `${endpointRoot}/${result}/timeline/chunk?${params}`,
{ headers: { Accept: "application/json" }, signal }, { headers: { Accept: "application/json" }, signal },
@@ -834,8 +851,12 @@ export async function fetchM4ThreatTimelineChunk(
if (parsedStart !== startSequence) { if (parsedStart !== startSequence) {
throw new M4ThreatContractError("M4.6 timeline chunk start: нарушен контракт."); throw new M4ThreatContractError("M4.6 timeline chunk start: нарушен контракт.");
} }
const frames = array(payload.frames, "M4.6 timeline frames").map((raw, offset) => const frames = array(payload.frames, "M4.6 timeline frames").map((raw, offset) => {
parseTimelineFrame(raw, result, parsedStart + offset, endpointRoot)); const parsed = parseTimelineFrame(raw, result, parsedStart + offset, endpointRoot);
return playbackPointPack
? hydrateM4ThreatTimelineFrame(parsed, playbackPointPack)
: parsed;
});
const parsedCount = integer(payload.frame_count, "M4.6 timeline chunk count"); const parsedCount = integer(payload.frame_count, "M4.6 timeline chunk count");
if (parsedCount !== frames.length || parsedCount > frameCount) { if (parsedCount !== frames.length || parsedCount > frameCount) {
throw new M4ThreatContractError("M4.6 timeline chunk count: нарушен контракт."); throw new M4ThreatContractError("M4.6 timeline chunk count: нарушен контракт.");
@@ -851,6 +872,159 @@ export async function fetchM4ThreatTimelineChunk(
}; };
} }
function playbackSha256Hex(buffer: ArrayBuffer): Promise<string> {
if (!globalThis.crypto?.subtle) {
throw new M4ThreatContractError("Браузер не поддерживает проверку playback SHA-256.");
}
return globalThis.crypto.subtle.digest("SHA-256", buffer).then((digest) => (
[...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("")
));
}
function roundedBodyCoordinate(value: number): number {
return Math.round(value * 1_000_000) / 1_000_000;
}
export function hydrateM4ThreatTimelineFrame(
frame: M4ThreatTimelineFrame,
pack: M4ThreatPlaybackPointPack,
): M4ThreatTimelineFrame {
if (frame.sequence >= pack.frameCount || pack.resultId === "") {
throw new M4ThreatContractError("M4.6 binary playback frame: нарушена идентичность.");
}
if (!frame.sourceAvailable || !frame.spatialAvailable || !frame.bodyFrame) {
if (frame.pointCloudSourceCount !== 0) {
throw new M4ThreatContractError("M4.6 binary playback unavailable frame содержит точки.");
}
return { ...frame, pointCloudBodyXyzM: [], pointCloudSampleCount: 0 };
}
const start = pack.pointOffsets[frame.sequence];
const stop = pack.pointOffsets[frame.sequence + 1];
if (start === undefined || stop === undefined || stop < start || stop > pack.pointCount) {
throw new M4ThreatContractError("M4.6 binary playback offsets: нарушена размерность.");
}
const count = stop - start;
if (frame.pointCloudSourceCount !== count || frame.pointCloudSampleCount !== count) {
throw new M4ThreatContractError("M4.6 binary playback point accounting: нарушен контракт.");
}
const origin = frame.bodyFrame.originMapXyzM;
const basis = frame.bodyFrame.basisMapFromBody;
const points = new Array<M4Point3>(count);
for (let index = 0; index < count; index += 1) {
const sourceOffset = (start + index) * 3;
const dx = pack.pointsMapXyzM[sourceOffset]! - origin[0];
const dy = pack.pointsMapXyzM[sourceOffset + 1]! - origin[1];
const dz = pack.pointsMapXyzM[sourceOffset + 2]! - origin[2];
points[index] = [
roundedBodyCoordinate(dx * basis[0][0] + dy * basis[1][0] + dz * basis[2][0]),
roundedBodyCoordinate(dx * basis[0][1] + dy * basis[1][1] + dz * basis[2][1]),
roundedBodyCoordinate(dx * basis[0][2] + dy * basis[1][2] + dz * basis[2][2]),
];
}
return { ...frame, pointCloudBodyXyzM: points, pointCloudSampleCount: count };
}
export async function fetchM4ThreatPlaybackPointPack(
result: string,
{
fetcher = fetch,
signal,
endpointRoot = M4_THREAT_TIMELINE_ENDPOINT_ROOT,
onProgress,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
endpointRoot?: string;
onProgress?: (progress: M4ThreatPlaybackProgress) => void;
} = {},
): Promise<M4ThreatPlaybackPointPack> {
resultId(result);
onProgress?.({ phase: "manifest", loadedBytes: 0, totalBytes: 0 });
const manifestResponse = await fetcher(`${endpointRoot}/${result}/timeline/playback`, {
headers: { Accept: "application/json" },
signal,
});
if (!manifestResponse.ok) {
throw new M4ThreatContractError(`M4.6 playback manifest: HTTP ${manifestResponse.status}.`);
}
const manifest = object(await manifestResponse.json(), "M4.6 playback manifest");
exact(manifest.schema_version, "missioncore.recorded-spatial-playback/v1", "M4.6 playback schema");
exact(manifest.result_id, result, "M4.6 playback result");
exact(manifest.coordinate_frame, "map", "M4.6 playback coordinate frame");
exact(manifest.access, "read-only-sealed-binary-playback", "M4.6 playback access");
const frameCount = exact(integer(manifest.frame_count, "M4.6 playback frames"), 4489, "M4.6 playback frames");
const pointCount = integer(manifest.point_count, "M4.6 playback points");
const offsetsRaw = array(manifest.point_offsets, "M4.6 playback offsets");
if (offsetsRaw.length !== frameCount + 1) {
throw new M4ThreatContractError("M4.6 playback offsets: неверная размерность.");
}
const pointOffsets = Uint32Array.from(offsetsRaw, (value) => integer(value, "M4.6 playback offset"));
if (pointOffsets[0] !== 0 || pointOffsets[pointOffsets.length - 1] !== pointCount) {
throw new M4ThreatContractError("M4.6 playback offsets: нарушено замыкание.");
}
for (let index = 1; index < pointOffsets.length; index += 1) {
if (pointOffsets[index]! < pointOffsets[index - 1]!) {
throw new M4ThreatContractError("M4.6 playback offsets: нарушена монотонность.");
}
}
const track = object(manifest.track, "M4.6 playback track");
exact(track.id, "points-map-f32", "M4.6 playback track id");
exact(track.dtype, "<f4", "M4.6 playback dtype");
const shape = array(track.shape, "M4.6 playback shape").map((value) => integer(value, "M4.6 playback shape"));
if (shape.length !== 2 || shape[0] !== pointCount || shape[1] !== 3) {
throw new M4ThreatContractError("M4.6 playback shape: нарушена размерность.");
}
const byteLength = integer(track.bytes, "M4.6 playback bytes");
if (byteLength !== pointCount * 3 * Float32Array.BYTES_PER_ELEMENT) {
throw new M4ThreatContractError("M4.6 playback bytes: нарушен accounting.");
}
const sha256 = text(track.sha256, "M4.6 playback SHA-256");
if (!/^[a-f0-9]{64}$/.test(sha256)) {
throw new M4ThreatContractError("M4.6 playback SHA-256: нарушен контракт.");
}
const response = await fetcher(text(track.url, "M4.6 playback URL"), {
headers: { Accept: "application/octet-stream" },
signal,
});
if (!response.ok) throw new M4ThreatContractError(`M4.6 playback points: HTTP ${response.status}.`);
const bytes = new Uint8Array(byteLength);
if (response.body) {
const reader = response.body.getReader();
let offset = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (offset + value.byteLength > bytes.byteLength) {
throw new M4ThreatContractError("M4.6 playback points превысил объявленный размер.");
}
bytes.set(value, offset);
offset += value.byteLength;
onProgress?.({ phase: "download", loadedBytes: offset, totalBytes: byteLength });
}
if (offset !== byteLength) {
throw new M4ThreatContractError("M4.6 playback points получен не полностью.");
}
} else {
const fallback = new Uint8Array(await response.arrayBuffer());
if (fallback.byteLength !== byteLength) {
throw new M4ThreatContractError("M4.6 playback points: неверный размер.");
}
bytes.set(fallback);
}
onProgress?.({ phase: "verify", loadedBytes: byteLength, totalBytes: byteLength });
if (await playbackSha256Hex(bytes.buffer) !== sha256) {
throw new M4ThreatContractError("M4.6 playback points: SHA-256 не совпал.");
}
onProgress?.({ phase: "ready", loadedBytes: byteLength, totalBytes: byteLength });
return {
resultId: result,
frameCount,
pointCount,
pointOffsets,
pointsMapXyzM: new Float32Array(bytes.buffer),
};
}
export async function fetchM4ThreatCameraPointOverlay( export async function fetchM4ThreatCameraPointOverlay(
result: string, result: string,
sequence: number, sequence: number,
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import type { import type {
RecordedEvidenceSemanticClass, RecordedEvidenceSemanticClass,
@@ -9,10 +9,11 @@ import {
type E47SemanticSlamResult, type E47SemanticSlamResult,
} from "../../core/laboratory/e47SemanticSlam"; } from "../../core/laboratory/e47SemanticSlam";
import { import {
fetchM49TgsFullShadowSpatialChunk, fetchM49TgsFullShadowPlaybackPack,
type M49TgsFullShadowResult, type M49TgsFullShadowResult,
type M49TgsFullShadowPlaybackPack,
type M49TgsFullShadowPlaybackProgress,
type M49TgsFullShadowSpatial, type M49TgsFullShadowSpatial,
type M49TgsFullShadowSpatialChunk,
type M49TgsFullShadowStateCode, type M49TgsFullShadowStateCode,
} from "../../core/laboratory/m49TgsFullShadow"; } from "../../core/laboratory/m49TgsFullShadow";
import { import {
@@ -32,9 +33,6 @@ const PALETTE: readonly RecordedEvidenceSemanticPaletteEntry[] = [
{ classId: 3, color: { kind: "token", token: "--nodedc-warning-rgb" } }, { classId: 3, color: { kind: "token", token: "--nodedc-warning-rgb" } },
]; ];
const CHUNK_FRAMES = 24;
const RETAINED_CHUNKS = 4;
function cellState(code: M49TgsFullShadowStateCode): M4ReplayClassifiedSpatialFrame["cellsMapGravityLocal"][number]["state"] { function cellState(code: M49TgsFullShadowStateCode): M4ReplayClassifiedSpatialFrame["cellsMapGravityLocal"][number]["state"] {
if (code === 1) return "ground-support"; if (code === 1) return "ground-support";
if (code === 2) return "nonground-occupied"; if (code === 2) return "nonground-occupied";
@@ -52,16 +50,14 @@ export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowR
const [activeSequence, setActiveSequence] = useState<number | null>(null); const [activeSequence, setActiveSequence] = useState<number | null>(null);
const [semantic, setSemantic] = useState<E47SemanticSlamResult | null>(null); const [semantic, setSemantic] = useState<E47SemanticSlamResult | null>(null);
const [semanticError, setSemanticError] = useState<string | null>(null); const [semanticError, setSemanticError] = useState<string | null>(null);
const [chunks, setChunks] = useState<ReadonlyMap<number, M49TgsFullShadowSpatialChunk>>( const [playbackPack, setPlaybackPack] = useState<M49TgsFullShadowPlaybackPack | null>(null);
() => new Map(), const [playbackProgress, setPlaybackProgress] = useState<M49TgsFullShadowPlaybackProgress>({
); phase: "manifest",
trackId: null,
loadedBytes: 0,
totalBytes: 0,
});
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const inFlightRef = useRef(new Map<number, AbortController>());
const activeChunkStart = activeSequence === null
? null
: Math.floor(activeSequence / CHUNK_FRAMES) * CHUNK_FRAMES;
const activeChunkStartRef = useRef(activeChunkStart);
activeChunkStartRef.current = activeChunkStart;
useEffect(() => { useEffect(() => {
const controller = new AbortController(); const controller = new AbortController();
@@ -85,65 +81,64 @@ export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowR
}, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId]); }, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId]);
useEffect(() => { useEffect(() => {
for (const controller of inFlightRef.current.values()) controller.abort(); const controller = new AbortController();
inFlightRef.current.clear(); setPlaybackPack(null);
setChunks(new Map()); setPlaybackProgress({ phase: "manifest", trackId: null, loadedBytes: 0, totalBytes: 0 });
setError(null); setError(null);
return () => { void fetchM49TgsFullShadowPlaybackPack(result.resultId, {
for (const controller of inFlightRef.current.values()) controller.abort(); signal: controller.signal,
inFlightRef.current.clear(); onProgress: setPlaybackProgress,
}; }).then((pack) => {
if (!controller.signal.aborted) setPlaybackPack(pack);
}).catch((caught: unknown) => {
if (!controller.signal.aborted) setError(message(caught));
});
return () => controller.abort();
}, [result.resultId]); }, [result.resultId]);
useEffect(() => {
if (activeChunkStart === null) return;
const desiredStarts = [activeChunkStart, activeChunkStart + CHUNK_FRAMES]
.filter((start) => start < result.timeline.frameCount);
const desired = new Set(desiredStarts);
for (const [start, controller] of inFlightRef.current) {
if (desired.has(start)) continue;
controller.abort();
inFlightRef.current.delete(start);
}
for (const start of desiredStarts) {
if (chunks.has(start) || inFlightRef.current.has(start)) continue;
const controller = new AbortController();
inFlightRef.current.set(start, controller);
void fetchM49TgsFullShadowSpatialChunk(result.resultId, start, CHUNK_FRAMES, {
signal: controller.signal,
})
.then((chunk) => {
if (controller.signal.aborted) return;
setChunks((current) => {
const next = new Map(current);
next.set(start, chunk);
const center = activeChunkStartRef.current ?? start;
const retained = [...next.keys()]
.sort((left, right) => Math.abs(left - center) - Math.abs(right - center))
.slice(0, RETAINED_CHUNKS);
return new Map(retained.map((key) => [key, next.get(key)!]));
});
if (start === activeChunkStartRef.current) setError(null);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted && start === activeChunkStartRef.current) {
setError(message(caught));
}
})
.finally(() => {
if (inFlightRef.current.get(start) === controller) inFlightRef.current.delete(start);
});
break;
}
}, [activeChunkStart, chunks, result.resultId, result.timeline.frameCount]);
const spatial = useMemo<M49TgsFullShadowSpatial | null>(() => { const spatial = useMemo<M49TgsFullShadowSpatial | null>(() => {
if (activeSequence === null || activeChunkStart === null) return null; if (activeSequence === null || !playbackPack) return null;
return chunks.get(activeChunkStart)?.frames.find( const frame = playbackPack.frames[activeSequence];
(frame) => frame.sourceSequence === activeSequence, if (!frame) return null;
) ?? null; const cellOffset = activeSequence * playbackPack.cellCount;
}, [activeChunkStart, activeSequence, chunks]); const zOffset = cellOffset * 2;
const states = Array.from(
playbackPack.states.subarray(cellOffset, cellOffset + playbackPack.cellCount),
(value) => value as M49TgsFullShadowStateCode,
);
const zBoundsM = Array.from({ length: playbackPack.cellCount }, (_, index) => {
const minimum = playbackPack.zBoundsM[zOffset + index * 2]!;
const maximum = playbackPack.zBoundsM[zOffset + index * 2 + 1]!;
return [
Number.isFinite(minimum) ? minimum : null,
Number.isFinite(maximum) ? maximum : null,
] as const;
});
return {
resultId: playbackPack.resultId,
sourceSequence: activeSequence,
sourceFrameIndex: frame.sourceFrameIndex,
sessionSeconds: frame.sessionSeconds,
sampleAvailable: frame.sampleAvailable,
costmap: {
cellSizeM: result.configuration.cellSizeM,
radiusM: result.configuration.radiusM,
centersXyM: playbackPack.centersXyM,
states,
zBoundsM,
},
metrics: frame.metrics,
};
}, [activeSequence, playbackPack, result.configuration.cellSizeM, result.configuration.radiusM]);
const loading = activeSequence !== null && !spatial && !error; const loading = activeSequence !== null && !spatial && !error;
const progressPercent = playbackProgress.totalBytes > 0
? Math.min(100, Math.round((playbackProgress.loadedBytes / playbackProgress.totalBytes) * 100))
: 0;
const loadingLabel = playbackProgress.phase === "manifest"
? "Проверяем playback manifest"
: playbackProgress.phase === "ready"
? "TGS playback готов"
: `Подготавливаем TGS playback · ${progressPercent}% · ${(playbackProgress.loadedBytes / 1_048_576).toFixed(1)}/${(playbackProgress.totalBytes / 1_048_576).toFixed(1)} МБ`;
const classifiedFrame = useMemo<M4ReplayClassifiedSpatialFrame | null>(() => { const classifiedFrame = useMemo<M4ReplayClassifiedSpatialFrame | null>(() => {
if (!spatial) return null; if (!spatial) return null;
@@ -188,6 +183,7 @@ export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowR
expectedAtSequence: true, expectedAtSequence: true,
frame: classifiedFrame, frame: classifiedFrame,
loading, loading,
loadingLabel,
error, error,
replacePointCloud: false, replacePointCloud: false,
}} }}
@@ -130,6 +130,7 @@ export interface M4ReplayClassifiedSpatialLayer {
expectedAtSequence: boolean; expectedAtSequence: boolean;
frame: M4ReplayClassifiedSpatialFrame | null; frame: M4ReplayClassifiedSpatialFrame | null;
loading: boolean; loading: boolean;
loadingLabel?: string;
error: string | null; error: string | null;
replacePointCloud?: boolean; replacePointCloud?: boolean;
} }
@@ -850,7 +851,9 @@ export function M4ReplayThreatVisual({
: activeSpatialFrame : activeSpatialFrame
? "map-gravity-local · all eligible points accounted · causal rolling 1 s" ? "map-gravity-local · all eligible points accounted · causal rolling 1 s"
: "TGS рассчитан · linked source cloud недоступен для этого кадра" : "TGS рассчитан · linked source cloud недоступен для этого кадра"
: classifiedSpatialLayer.error ?? `Открываем ${classifiedSpatialLayer.label}` : classifiedSpatialLayer.error
?? classifiedSpatialLayer.loadingLabel
?? `Открываем ${classifiedSpatialLayer.label}`
: ( : (
<> <>
{spatialFrame {spatialFrame
@@ -1022,7 +1025,7 @@ export function M4ReplayThreatVisual({
: <Icon name="alert" size={18} />} : <Icon name="alert" size={18} />}
<span>{classifiedSpatialLayer.error <span>{classifiedSpatialLayer.error
?? (classifiedSpatialLayer.loading || displayingBufferedFrame ?? (classifiedSpatialLayer.loading || displayingBufferedFrame
? `Открываем ${classifiedSpatialLayer.label}` ? classifiedSpatialLayer.loadingLabel ?? `Открываем ${classifiedSpatialLayer.label}`
: classifiedSpatialLayer.expectedAtSequence : classifiedSpatialLayer.expectedAtSequence
? `Открываем ${classifiedSpatialLayer.label}` ? `Открываем ${classifiedSpatialLayer.label}`
: `${classifiedSpatialLayer.label} рассчитан только на 10 контрольных кадров.`)}</span> : `${classifiedSpatialLayer.label} рассчитан только на 10 контрольных кадров.`)}</span>
@@ -1071,7 +1074,7 @@ export function M4ReplayThreatVisual({
{timelineFrame.loading || displayingBufferedFrame ? ( {timelineFrame.loading || displayingBufferedFrame ? (
<div className="m4-replay-threat-visual__buffering" role="status"> <div className="m4-replay-threat-visual__buffering" role="status">
<span className="busy-indicator" aria-hidden="true" /> <span className="busy-indicator" aria-hidden="true" />
<span>Догружаем следующий spatial-буфер без сброса сцены</span> <span>{timelineFrame.loadingLabel ?? "Догружаем следующий spatial-буфер без сброса сцены"}</span>
</div> </div>
) : null} ) : null}
{timelineFrame.error ? ( {timelineFrame.error ? (
@@ -2,10 +2,14 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { import {
fetchM4ThreatCameraPointOverlay, fetchM4ThreatCameraPointOverlay,
fetchM4ThreatPlaybackPointPack,
fetchM4ThreatTimeline, fetchM4ThreatTimeline,
fetchM4ThreatTimelineChunk, fetchM4ThreatTimelineChunk,
M4_THREAT_TIMELINE_ENDPOINT_ROOT,
selectM4ThreatTimelineSequence, selectM4ThreatTimelineSequence,
type M4ThreatCameraPointOverlay, type M4ThreatCameraPointOverlay,
type M4ThreatPlaybackPointPack,
type M4ThreatPlaybackProgress,
type M4ThreatTimeline, type M4ThreatTimeline,
type M4ThreatTimelineChunk, type M4ThreatTimelineChunk,
type M4ThreatTimelineFrame, type M4ThreatTimelineFrame,
@@ -82,11 +86,48 @@ export function useM4ThreatTimelineFrame({
() => new Map(), () => new Map(),
); );
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [pointPack, setPointPack] = useState<M4ThreatPlaybackPointPack | null>(null);
const [playbackProgress, setPlaybackProgress] = useState<M4ThreatPlaybackProgress>({
phase: "manifest",
loadedBytes: 0,
totalBytes: 0,
});
const [playbackError, setPlaybackError] = useState<string | null>(null);
const binaryPlayback = endpointRoot === undefined
|| endpointRoot === M4_THREAT_TIMELINE_ENDPOINT_ROOT;
const inFlight = useRef(new Map<number, AbortController>()); const inFlight = useRef(new Map<number, AbortController>());
const chunksRef = useRef(chunks); const chunksRef = useRef(chunks);
const activeChunkStartRef = useRef<number | null>(null); const activeChunkStartRef = useRef<number | null>(null);
chunksRef.current = chunks; chunksRef.current = chunks;
useEffect(() => {
const controller = new AbortController();
setPointPack(null);
setPlaybackError(null);
setPlaybackProgress({ phase: "manifest", loadedBytes: 0, totalBytes: 0 });
if (!timeline) return () => controller.abort();
if (!binaryPlayback) {
setPlaybackProgress({ phase: "ready", loadedBytes: 0, totalBytes: 0 });
return () => controller.abort();
}
void fetchM4ThreatPlaybackPointPack(resultId, {
signal: controller.signal,
endpointRoot,
onProgress: (progress) => {
if (!controller.signal.aborted) setPlaybackProgress(progress);
},
})
.then((pack) => {
if (!controller.signal.aborted) setPointPack(pack);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted) {
setPlaybackError(errorMessage(caught, "Бинарный spatial playback M4.6 недоступен."));
}
});
return () => controller.abort();
}, [binaryPlayback, endpointRoot, resultId, timeline]);
useEffect(() => { useEffect(() => {
for (const controller of inFlight.current.values()) controller.abort(); for (const controller of inFlight.current.values()) controller.abort();
inFlight.current.clear(); inFlight.current.clear();
@@ -116,7 +157,7 @@ export function useM4ThreatTimelineFrame({
activeChunkStartRef.current = activeChunkStart; activeChunkStartRef.current = activeChunkStart;
useEffect(() => { useEffect(() => {
if (!timeline || activeChunkStart === null) return; if (!timeline || activeChunkStart === null || (binaryPlayback && !pointPack)) return;
const starts = m4ThreatChunkWindowStarts( const starts = m4ThreatChunkWindowStarts(
activeChunkStart, activeChunkStart,
chunkSize, chunkSize,
@@ -131,6 +172,7 @@ export function useM4ThreatTimelineFrame({
signal: controller.signal, signal: controller.signal,
endpointRoot, endpointRoot,
cameraObstacleProjectionDelivery: timeline.cameraObstacleProjectionDelivery, cameraObstacleProjectionDelivery: timeline.cameraObstacleProjectionDelivery,
playbackPointPack: binaryPlayback ? pointPack ?? undefined : undefined,
}) })
.then((chunk) => { .then((chunk) => {
if (controller.signal.aborted) return; if (controller.signal.aborted) return;
@@ -161,7 +203,7 @@ export function useM4ThreatTimelineFrame({
// loaded first, then the next chunk is prefetched on the following render. // loaded first, then the next chunk is prefetched on the following render.
break; break;
} }
}, [activeChunkStart, chunkSize, chunks, endpointRoot, resultId, timeline]); }, [activeChunkStart, binaryPlayback, chunkSize, chunks, endpointRoot, pointPack, resultId, timeline]);
const activeFrame: M4ThreatTimelineFrame | null = useMemo(() => { const activeFrame: M4ThreatTimelineFrame | null = useMemo(() => {
if (activeSequence === null || activeChunkStart === null) return null; if (activeSequence === null || activeChunkStart === null) return null;
@@ -181,8 +223,17 @@ export function useM4ThreatTimelineFrame({
activeSequence, activeSequence,
activeFrame, activeFrame,
availableFrames, availableFrames,
loading: error === null && Boolean(timeline) && !activeFrame, loading: error === null && playbackError === null && Boolean(timeline) && !activeFrame,
error, loadingLabel: !binaryPlayback
? "Догружаем следующий spatial-буфер без сброса сцены"
: playbackProgress.phase === "manifest"
? "Проверяем M4 playback manifest"
: playbackProgress.phase === "ready"
? "Подготавливаем текущий spatial-кадр"
: `${playbackProgress.phase === "verify" ? "Проверяем" : "Загружаем"} M4 spatial playback · ${playbackProgress.totalBytes > 0
? `${Math.min(100, Math.round(playbackProgress.loadedBytes / playbackProgress.totalBytes * 100))}% · ${(playbackProgress.loadedBytes / 1_048_576).toFixed(1)}/${(playbackProgress.totalBytes / 1_048_576).toFixed(1)} МБ`
: "0%"}`,
error: playbackError ?? error,
}; };
} }
@@ -6,6 +6,7 @@ import { createServer } from "vite";
let server; let server;
let fetchM49TgsFullShadowSpatialChunk; let fetchM49TgsFullShadowSpatialChunk;
let parseM49TgsNpyTrack;
const resultId = `m49-tgs-full-shadow-${"a".repeat(64)}`; const resultId = `m49-tgs-full-shadow-${"a".repeat(64)}`;
@@ -15,7 +16,7 @@ before(async () => {
logLevel: "silent", logLevel: "silent",
server: { middlewareMode: true }, server: { middlewareMode: true },
}); });
({ fetchM49TgsFullShadowSpatialChunk } = await server.ssrLoadModule( ({ fetchM49TgsFullShadowSpatialChunk, parseM49TgsNpyTrack } = await server.ssrLoadModule(
"/src/core/laboratory/m49TgsFullShadow.ts", "/src/core/laboratory/m49TgsFullShadow.ts",
)); ));
}); });
@@ -73,7 +74,7 @@ test("M4.9T5 chunk keeps every missing-LiDAR cell explicitly UNOBSERVED", async
assert.deepEqual(new Set(chunk.frames[0].costmap.states), new Set([0])); assert.deepEqual(new Set(chunk.frames[0].costmap.states), new Set([0]));
}); });
test("M4.9T5 viewer prefetches 24-frame immutable chunks", async () => { test("M4.9T5 viewer preloads one immutable binary playback pack", async () => {
const [source, contract] = await Promise.all([ const [source, contract] = await Promise.all([
readFile( readFile(
new URL("../src/workspaces/laboratory/M49TgsFullShadowEvidence.tsx", import.meta.url), new URL("../src/workspaces/laboratory/M49TgsFullShadowEvidence.tsx", import.meta.url),
@@ -84,12 +85,37 @@ test("M4.9T5 viewer prefetches 24-frame immutable chunks", async () => {
"utf8", "utf8",
), ),
]); ]);
assert.match(source, /const CHUNK_FRAMES = 24/); assert.match(source, /fetchM49TgsFullShadowPlaybackPack/);
assert.match(source, /activeChunkStart \+ CHUNK_FRAMES/); assert.match(source, /playbackProgress/);
assert.match(source, /fetchM49TgsFullShadowSpatialChunk/); assert.doesNotMatch(source, /fetchM49TgsFullShadowSpatialChunk/);
assert.match(source, /sampleAvailable: spatial\.sampleAvailable/); assert.match(source, /sampleAvailable: spatial\.sampleAvailable/);
assert.match(source, /fetchE47SemanticSlamResult/); assert.match(source, /fetchE47SemanticSlamResult/);
assert.match(source, /next\.baseM4ResultId !== result\.source\.linkedVisualResultId/); assert.match(source, /next\.baseM4ResultId !== result\.source\.linkedVisualResultId/);
assert.match(source, /semantic=\{semantic \? \{/); assert.match(source, /semantic=\{semantic \? \{/);
assert.match(contract, /linked_semantic_result_id/); assert.match(contract, /linked_semantic_result_id/);
assert.match(contract, /missioncore\.m49-tgs-full-shadow-playback\/v1/);
});
function npyFloat32(values, shape) {
const shapeText = shape.length === 1 ? `${shape[0]},` : shape.join(", ");
const prefixLength = 10;
let header = `{'descr': '<f4', 'fortran_order': False, 'shape': (${shapeText}), }`;
const padding = (16 - ((prefixLength + header.length + 1) % 16)) % 16;
header += " ".repeat(padding) + "\n";
const buffer = new ArrayBuffer(prefixLength + header.length + values.length * 4);
const bytes = new Uint8Array(buffer);
bytes.set([0x93, 0x4e, 0x55, 0x4d, 0x50, 0x59, 1, 0]);
new DataView(buffer).setUint16(8, header.length, true);
bytes.set(new TextEncoder().encode(header), prefixLength);
new Float32Array(buffer, prefixLength + header.length, values.length).set(values);
return buffer;
}
test("M4.9T5 parses sealed NPY tracks without JSON point arrays", () => {
const parsed = parseM49TgsNpyTrack(
npyFloat32([1.25, -2.5, 3.75, 4.5], [2, 2]),
"<f4",
[2, 2],
);
assert.deepEqual([...parsed], [1.25, -2.5, 3.75, 4.5]);
}); });
@@ -10,6 +10,7 @@ let fetchM4ThreatVisual;
let fetchM4ThreatTimeline; let fetchM4ThreatTimeline;
let fetchM4ThreatTimelineChunk; let fetchM4ThreatTimelineChunk;
let fetchM4ThreatCameraPointOverlay; let fetchM4ThreatCameraPointOverlay;
let hydrateM4ThreatTimelineFrame;
let selectM4ThreatTimelineFrame; let selectM4ThreatTimelineFrame;
let selectM4ThreatTimelineSequence; let selectM4ThreatTimelineSequence;
let advanceRecordedEvidencePlayback; let advanceRecordedEvidencePlayback;
@@ -33,6 +34,7 @@ before(async () => {
fetchM4ThreatTimeline, fetchM4ThreatTimeline,
fetchM4ThreatTimelineChunk, fetchM4ThreatTimelineChunk,
fetchM4ThreatCameraPointOverlay, fetchM4ThreatCameraPointOverlay,
hydrateM4ThreatTimelineFrame,
selectM4ThreatTimelineFrame, selectM4ThreatTimelineFrame,
selectM4ThreatTimelineSequence, selectM4ThreatTimelineSequence,
} = await server.ssrLoadModule("/src/core/laboratory/m4ReplayThreat.ts")); } = await server.ssrLoadModule("/src/core/laboratory/m4ReplayThreat.ts"));
@@ -332,6 +334,45 @@ test("M4.6 timeline keeps only a compact index and decodes bounded spatial chunk
assert.equal(selectM4ThreatTimelineFrame(chunk.frames, 35.50).sequence, 1); assert.equal(selectM4ThreatTimelineFrame(chunk.frames, 35.50).sequence, 1);
}); });
test("M4.6 hydrates a lightweight timeline frame from one retained binary point track", async () => {
const raw = timelineFrame(0, 35.421857292, {
body_frame: {
origin_map_xyz_m: [10, 20, 30],
basis_map_from_body: [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
},
point_cloud_body_xyz_m: [],
point_cloud_source_count: 2,
point_cloud_sample_count: 2,
});
const parsed = await fetchM4ThreatTimelineChunk(resultId, 0, 1, {
playbackPointPack: {
resultId,
frameCount: 4489,
pointCount: 2,
pointOffsets: new Uint32Array([0, 2, ...Array(4488).fill(2)]),
pointsMapXyzM: new Float32Array([11, 20, 30.25, 12, 19.5, 30]),
},
fetcher: async (input) => {
assert.match(String(input), /include_points=false/);
return new Response(JSON.stringify({
schema_version: "missioncore.recorded-spatial-evidence-chunk/v1",
result_id: resultId,
start_sequence: 0,
frame_count: 1,
next_sequence: 1,
frames: [raw],
authority: "replay-simulated",
}), { status: 200 });
},
});
assert.deepEqual(parsed.frames[0].pointCloudBodyXyzM, [
[1, 0, 0.25],
[2, -0.5, 0],
]);
assert.equal(parsed.frames[0].pointCloudSampleCount, 2);
assert.equal(typeof hydrateM4ThreatTimelineFrame, "function");
});
test("M4.8S timeline binds factory-KB4 camera points through its exact endpoint", async () => { 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 replayResultId = `m48s-fixed-class-detector-lab-${"b".repeat(64)}`;
const endpointRoot = "/api/v1/laboratory/m48s/fixed-class-detector"; const endpointRoot = "/api/v1/laboratory/m48s/fixed-class-detector";
@@ -28,6 +28,8 @@ test("semantic evidence mask is GPU-colored, bounded, cancelled and object-conta
assert.match(source, /Math\.min\(width \/ mask\.width, height \/ mask\.height\)/); assert.match(source, /Math\.min\(width \/ mask\.width, height \/ mask\.height\)/);
assert.match(source, /decoded\.key !== expectedKey/); assert.match(source, /decoded\.key !== expectedKey/);
assert.match(source, /mask\?\.key === expectedKey/); assert.match(source, /mask\?\.key === expectedKey/);
assert.match(source, /lastReadyMaskRef/);
assert.match(source, /data-state=\{failure \? "error" : exactMask \? "ready" : renderMask \? "stale" : "loading"\}/);
assert.match(source, /recorded-evidence-semantic-mask-overlay__error/); assert.match(source, /recorded-evidence-semantic-mask-overlay__error/);
assert.match(source, /role="alert"/); assert.match(source, /role="alert"/);
}); });
@@ -53,6 +55,9 @@ test("metric evidence keeps missing semantic assignments as context and exposes
assert.match(source, /classId === null \? undefined : colorsByClassId\.get\(classId\)/); assert.match(source, /classId === null \? undefined : colorsByClassId\.get\(classId\)/);
assert.match(source, /data-decision="semantic"/); assert.match(source, /data-decision="semantic"/);
assert.match(source, /recordedEvidenceSemanticCssColor/); assert.match(source, /recordedEvidenceSemanticCssColor/);
assert.match(source, /DynamicDrawUsage/);
assert.match(source, /classifiedMeshesRef/);
assert.match(source, /updatePointPositions/);
}); });
test("semantic point alignment follows the last qualified spatial increment", async () => { test("semantic point alignment follows the last qualified spatial increment", async () => {
@@ -0,0 +1,73 @@
{
"schema_version": "missioncore.m49-tgs-integrated-graph-shadow-profile/v1",
"profile_id": "m49-ravnoves00-tgs-native-risk-integrated-shadow/v1",
"source": {
"source_id": "RAVNOVES00",
"expected_timeline_frames": 4489,
"expected_available_lidar_frames": 3928,
"source_pack_sha256": "0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944",
"requested_source_rate_hz": 12.0,
"shared_start_barrier": true
},
"stages": {
"reference_graph": {
"graph_config": "m48n-rf-detr-native-reference-graph-shadow-v0.json",
"graph_config_sha256": "1db6f6fa0561d819505a5e4f62fe256dab6fe4d9da6b4f1a9a5073bfcf772c90",
"detector_profile": "rf-detr-large-native-kb4-risk-shadow-v0.json",
"detector_profile_sha256": "dbf4da5dbad6c3c22b1280b46ffcad81719bd183c81c263a4859847d829019b6",
"detector_provider_id": "triton-rf-detr-large-coco-native-kb4-risk-fp16-shadow/v0",
"single_inference_pass": true
},
"tgs": {
"profile": "m49-tgs-full-shadow-v1.json",
"profile_sha256": "c2e07010aaee78259d36c057962d6bfb885349251ff7356d867e5813e632881c",
"candidate_id": "travel-tgs-only-gravity-aligned",
"linked_accepted_result_id": "m49-tgs-full-shadow-ef98de7db7596d48e8c8c0549ce68e6704ee03e87c8c4bcf1e3e748b7ccb032e",
"aos_allowed": false,
"gpu_allowed": false,
"states_remain_separate": true
}
},
"acceptance": {
"minimum_delivery_ratio": 1.0,
"reference_world_state_fps": 11.79902,
"maximum_world_state_fps_regression_fraction": 0.05,
"minimum_effective_world_state_fps": 11.209069,
"maximum_world_state_completion_p95_ms": 125.0,
"candidate_stage_p95_ms_max": 25.0,
"candidate_stage_p99_ms_max": 50.0,
"combined_output_age_p99_ms_max": 125.0,
"capacity_drop_count_max": 0,
"unaccounted_frame_count_max": 0
},
"telemetry": {
"sample_interval_seconds": 1.0,
"required_products": [
"graph_pipeline_timing",
"graph_queue_high_watermarks",
"tgs_stage_timing",
"host_container_cpu_memory",
"gpu_utilization_memory_power_temperature"
]
},
"invariants": {
"native_fisheye_raster_unchanged": true,
"camera_rectification_allowed": false,
"camera_resize_allowed": false,
"tgs_candidate_parameters_unchanged": true,
"tgs_modifies_reference_graph_state": false,
"missing_lidar_means_unobserved": true,
"future_frames_used": false,
"low_step_used": false,
"gauss_or_playcanvas_in_scope": false
},
"authority": {
"visual_quality_accepted": false,
"traversability_accepted": false,
"physical_free_space_accepted": false,
"commands_enabled": false,
"actuation_allowed": false,
"navigation_or_safety_accepted": false,
"production_accepted": false
}
}
@@ -572,6 +572,16 @@ Exit:
### M4.9 — recorded-realtime release candidate and cutover ### M4.9 — recorded-realtime release candidate and cutover
The first complete integrated source-paced load gate passed on `2026-08-27`:
the frozen RF-DETR world-state graph and unchanged CPU TGS candidate accounted
for all `4,489` frames at `11.860865 FPS`, with joined output p95/p99 of
`46.825886/63.94485 ms` and zero capacity drops. The immutable identities and
resource envelope are recorded in
[`experiments/perception/M49_TGS_INTEGRATED_GRAPH_ACCEPTANCE_2026-08-27.md`](../experiments/perception/M49_TGS_INTEGRATED_GRAPH_ACCEPTANCE_2026-08-27.md).
This closes only the one-loop integrated load gate. Visual traversability,
uncapped parity, repeated-loop soak, restart/recovery, navigation, actuation
and production remain open.
Execute on Worker 006 with the frozen RAVNOVES00 source: Execute on Worker 006 with the frozen RAVNOVES00 source:
1. one full `1.0×` 448.723-second replay; 1. one full `1.0×` 448.723-second replay;
@@ -344,6 +344,17 @@ This accepts the CPU source-paced shadow and retains the candidate. It does not
accept visual traversability quality, integrated graph performance, navigation accept visual traversability quality, integrated graph performance, navigation
or actuation. or actuation.
The product-integration load gate then passed on `2026-08-27`. The unchanged
TGS candidate ran concurrently with the frozen RF-DETR world-state graph for
all `4,489` frames at a requested `12 Hz`: every graph frame was delivered,
every TGS frame was accounted and capacity drops remained zero. The joined
output sustained `11.860865 FPS`, measured `46.825886/63.94485 ms` at p95/p99,
and the CPU-only TGS stage measured `1.735/2.14019 ms` at p95/p99 with a
`22.6 MiB` memory maximum. See
[`experiments/perception/M49_TGS_INTEGRATED_GRAPH_ACCEPTANCE_2026-08-27.md`](../experiments/perception/M49_TGS_INTEGRATED_GRAPH_ACCEPTANCE_2026-08-27.md).
Integrated runtime capacity is accepted; visual traversability, physical free
space, navigation, actuation and production remain unaccepted.
### T4 — Candidate C occupancy/ESDF probe ### T4 — Candidate C occupancy/ESDF probe
- Run nvblox separately with RF-DETR disabled for the isolated probe. - Run nvblox separately with RF-DETR disabled for the isolated probe.
@@ -361,15 +372,16 @@ or actuation.
## Immediate next action ## Immediate next action
Review the published full gravity-aligned TGS timeline in camera, metric 3D and Keep the accepted integrated graph frozen and review the published full
costmap space. Preserve gravity-aligned TGS timeline in camera, metric 3D and costmap space. Preserve
`GROUND_SUPPORT`, `NONGROUND_OCCUPIED`, `UNKNOWN_REJECTED` and `UNOBSERVED` as `GROUND_SUPPORT`, `NONGROUND_OCCUPIED`, `UNKNOWN_REJECTED` and `UNOBSERVED` as
separate products; do not use AOS or infer free cells from absent separate products; do not use AOS or infer free cells from absent
republication. Attach the unchanged candidate to the realtime world-state graph republication. Integrated FPS, latency, resources and queue behavior are now
in non-authoritative shadow mode and measure complete-graph FPS, latency, accepted for the one-loop source-paced gate. If occupied evidence carpets the
resource use and queue drops. If occupied evidence carpets the route, soft route, soft traversable vegetation or usable gaps, reject TGS without tuning it
traversable vegetation or usable gaps, reject TGS without tuning it against the against the review evidence. Close and induce-test the Worker inference-runtime
review evidence. Candidate A T2 replay remains blocked by the failed unmodified restart guard, then proceed to repeated-loop soak only if visual review also passes.
T1 gate. No LOW-STEP tuning, new object model, camera resize, fisheye Candidate A T2 replay remains blocked by the failed unmodified T1 gate. No
LOW-STEP tuning, new object model, camera resize, fisheye
rectification, manual dataset or parallel heavy Worker job is authorized by rectification, manual dataset or parallel heavy Worker job is authorized by
this decision. this decision.
@@ -0,0 +1,123 @@
# M4.9T5 integrated TGS + reference graph shadow — 2026-08-27
Status: **complete source-paced integrated shadow accepted**. Integrated
runtime capacity is accepted for the frozen RAVNOVES00 graph. Visual
traversability quality, physical free space, navigation, actuation and
production remain unaccepted.
## Decision
The unchanged gravity-aligned TGS candidate and the frozen RF-DETR
world-state graph completed the same `4,489`-frame RAVNOVES00 timeline from a
shared `12 Hz` start barrier on Worker 006. Every graph frame was delivered,
every TGS frame was accounted, all queues remained bounded and no capacity
drop or supersession occurred.
The integrated graph sustained `11.860865 FPS`, compared with the frozen
reference result at `11.79902 FPS`. TGS added `1.735 ms` at p95 and used at
most `22.6 MiB` of container memory; it did not add a GPU model. The exact
per-frame join of graph and TGS outputs measured `46.825886 ms` at p95 and
`63.94485 ms` at p99. This accepts the runtime integration gate without
changing any navigation or safety authority.
## Frozen identity
| Item | Identity |
| --- | --- |
| Integrated result | `m49-tgs-integrated-graph-shadow-75e48fd8acf0246be16613e087fcf26fe909f7834718d217c74785a57f494b24` |
| Worker run | `Worker 006 / ravnoves00-integrated-12fps-003` |
| Mission Core revision used by Worker | `f153d671d316ea7b2200c0edcb80206d896ffcd5` |
| Deterministic Worker artifact | `1818ea57887db819e9d986713b050bd15eae1a43ee4af70c5b8b5d39f4cd8522` |
| Worker wheel | `b9414a1f2f1998543aeb63824d3cdfe55bbc6e4eecc3146d65962ebc7da86b8b` |
| TGS native binary | `2c3b4c7e6b66d19d2e32ee3ef867fa8c14073be29441bcd19fe565d9632c6538` |
| Integrated profile | `b61e018b2d04eec58802e2d4186ce7a3dd3a15b254db106b57b609e903eeef80` |
| Frozen graph config | `1db6f6fa0561d819505a5e4f62fe256dab6fe4d9da6b4f1a9a5073bfcf772c90` |
| Frozen TGS profile | `c2e07010aaee78259d36c057962d6bfb885349251ff7356d867e5813e632881c` |
| Linked accepted TGS result | `m49-tgs-full-shadow-ef98de7db7596d48e8c8c0549ce68e6704ee03e87c8c4bcf1e3e748b7ccb032e` |
The release was built from `git archive` of the declared revision. Uncommitted
GAUSS and PlayCanvas files were neither packaged nor changed by the run.
## Full-run result
| Measurement | Result |
| --- | ---: |
| Graph admitted / delivered | `4,489 / 4,489` |
| TGS timeline accounted | `4,489 / 4,489` |
| LiDAR available | `3,928` |
| Delivery ratio | `1.0` |
| Capacity drops / supersessions / failures | `0 / 0 / 0` |
| Effective world-state rate | `11.860865 FPS` |
| Frozen reference rate | `11.79902 FPS` |
| World-state completion p50 / p95 / p99 / max | `28.437492 / 46.696395 / 63.94485 / 170.202343 ms` |
| Joined graph+TGS output p50 / p95 / p99 / max | `28.57367 / 46.825886 / 63.94485 / 170.202343 ms` |
| TGS stage p50 / p95 / p99 / max | `1.186 / 1.735 / 2.14019 / 6.151 ms` |
| TGS completion p50 / p95 / p99 / max | `17.605461 / 31.771832 / 42.957350 / 70.851977 ms` |
| Queue high-water marks | `2` for every bounded stage |
| Worker wrapper wall time | `544.887447 s` |
All nineteen formal identity, accounting, throughput, latency, telemetry,
capacity and false-authority checks passed.
## Resource envelope
| Resource | Result |
| --- | ---: |
| GPU utilization mean / p95 / max | `43.114973 / 53 / 57 %` |
| GPU memory mean / p95 / max | `9,737.144 / 9,744 / 9,751 MiB` |
| GPU power p95 / max | `149.342 / 153.81 W` |
| TGS CPU mean / p95 / max | `3.474793 / 5.27 / 6.04 %` |
| TGS memory mean / p95 / max | `20.571818 / 21.84 / 22.6 MiB` |
| Graph container memory max | `1,769.472 MiB` |
| Triton container memory max | `582.9 MiB` |
The measured GPU envelope belongs to the already accepted reference graph.
TGS is a CPU-only stage and does not request CUDA, Vulkan or a second model.
## LAB publication boundary
No third visual viewer is created for this gate. The TGS cells, source points,
camera frames and semantic archive are byte-identical to the already published
`m49-tgs-full-shadow` LAB. This run proves concurrent runtime behavior, so its
new evidence is the immutable timing, queue and resource report above. Visual
quality review continues in the existing full-shadow LAB.
## Runtime recovery finding
Preflight found the canonical `ndc-mission-core-triton` container stopped after
the Docker runtime restarted. Docker reported exit code `255`, no OOM event and
Compose configured `restart: "no"`. The accepted run began only after the exact
same container was started and healthy; its container ID remained
`4232fb040062a8384809e73612baa92b343ddcb60f00c23e7091eb4909959223`
throughout the run.
This is a deployment-lifecycle defect, not a perception overload result. The
container must use a durable restart policy and an unhealthy-state watchdog;
changing the TGS or detector graph cannot fix it.
The live container was subsequently changed in place to
`restart: unless-stopped`. Docker confirmed the service remained `running` and
`healthy` with the same container ID, so applying the mitigation caused no
inference restart. This protects the current container from process and Docker
daemon restarts. The versioned Worker Compose source still declares
`restart: "no"`; closing that source-level drift and inducing a controlled
recovery remain a separate deployment gate.
## Sealed compact evidence
| File | SHA-256 |
| --- | --- |
| `result.json` | `bfc881aa943d46582a248b6024a413c3c4b3a6959fa4a674f6ec33d658fdcbdf` |
| `graph-result.json` | `784d27b908137843f6adf08d267576a74b0676f5f98226923399ae59f538b78a` |
| `tgs-result.json` | `c0245744892b66c7f24af3cd7ea9c8cd8d543114fffa70e32d8d5fef81054716` |
| `worker-summary.json` | `c449246d14fb927b6490bcf657950b354f101fd3eb4b296bc0f6dd8543aafe10` |
## Next gate
Keep this integrated graph frozen. Complete the independent visual
traversability review in the existing TGS LAB, concentrating on vegetation,
compact obstacles, false occupied carpets, usable gaps and missing-LiDAR
behavior. In parallel, close the Worker inference-runtime restart policy and
verify an induced stop/recovery without changing result identity. Only after
both gates pass may the candidate advance to repeated-loop soak and recovery
acceptance; navigation and actuation remain off.
@@ -67,6 +67,27 @@ AUTHORITY: Final = {
LOAD_PURPOSES: Final = ("production-rate", "reserve-gate", "limit-discovery") LOAD_PURPOSES: Final = ("production-rate", "reserve-gate", "limit-discovery")
def wait_for_shared_start(
*,
ready_file: Path,
start_file: Path,
timeout_seconds: float,
) -> None:
"""Join an external source-admission barrier after local warmup is complete."""
if timeout_seconds <= 0:
raise RuntimeError("shared-start timeout must be positive")
if ready_file.exists():
raise RuntimeError("shared-start ready file already exists")
ready_file.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
ready_file.write_text("ready\n", encoding="utf-8")
deadline = time.monotonic() + timeout_seconds
while not start_file.is_file():
if time.monotonic() >= deadline:
raise RuntimeError("shared-start barrier timed out")
time.sleep(0.01)
class GpuTelemetry: class GpuTelemetry:
def __init__(self, interval_seconds: float) -> None: def __init__(self, interval_seconds: float) -> None:
self.interval_seconds = interval_seconds self.interval_seconds = interval_seconds
@@ -394,6 +415,9 @@ def main() -> int:
parser.add_argument("--runtime-artifact-sha256", required=True) parser.add_argument("--runtime-artifact-sha256", required=True)
parser.add_argument("--runner-sha256", required=True) parser.add_argument("--runner-sha256", required=True)
parser.add_argument("--telemetry-interval-seconds", type=float, default=1.0) parser.add_argument("--telemetry-interval-seconds", type=float, default=1.0)
parser.add_argument("--shared-start-ready-file", type=Path)
parser.add_argument("--shared-start-file", type=Path)
parser.add_argument("--shared-start-timeout-seconds", type=float, default=600.0)
parser.add_argument("--output", type=Path, required=True) parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--progress", type=Path, required=True) parser.add_argument("--progress", type=Path, required=True)
parser.add_argument("--frame-ledger", type=Path, required=True) parser.add_argument("--frame-ledger", type=Path, required=True)
@@ -404,6 +428,17 @@ def main() -> int:
raise RuntimeError("maximum frame count must be positive") raise RuntimeError("maximum frame count must be positive")
if arguments.telemetry_interval_seconds <= 0: if arguments.telemetry_interval_seconds <= 0:
raise RuntimeError("telemetry interval must be positive") raise RuntimeError("telemetry interval must be positive")
shared_start_requested = (
arguments.shared_start_ready_file is not None or arguments.shared_start_file is not None
)
if shared_start_requested and (
arguments.shared_start_ready_file is None or arguments.shared_start_file is None
):
raise RuntimeError("shared-start ready and start files must be configured together")
if shared_start_requested and arguments.loops != 1:
raise RuntimeError("shared-start barrier requires exactly one loop")
if arguments.shared_start_timeout_seconds <= 0:
raise RuntimeError("shared-start timeout must be positive")
if arguments.source_rate_hz is not None and ( if arguments.source_rate_hz is not None and (
not np.isfinite(arguments.source_rate_hz) or arguments.source_rate_hz <= 0 not np.isfinite(arguments.source_rate_hz) or arguments.source_rate_hz <= 0
): ):
@@ -506,6 +541,12 @@ def main() -> int:
) )
detector_warmup = runtime.warm_up_detector() detector_warmup = runtime.warm_up_detector()
source_prefetch = runtime.prepare_source() source_prefetch = runtime.prepare_source()
if shared_start_requested:
wait_for_shared_start(
ready_file=arguments.shared_start_ready_file,
start_file=arguments.shared_start_file,
timeout_seconds=arguments.shared_start_timeout_seconds,
)
gc_policy = CyclicGcHotLoopPolicy() gc_policy = CyclicGcHotLoopPolicy()
with gc_policy: with gc_policy:
runtime.mark_source_admission_started() runtime.mark_source_admission_started()
@@ -0,0 +1,455 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$ReleaseRoot,
[Parameter(Mandatory = $true)]
[string]$CandidateRoot,
[Parameter(Mandatory = $true)]
[ValidatePattern("^[a-f0-9]{64}$")]
[string]$ExpectedArtifactSha256,
[Parameter(Mandatory = $true)]
[ValidatePattern("^[A-Za-z0-9._-]{1,96}$")]
[string]$RunId,
[ValidateRange(1.0, 120.0)]
[double]$SourceRateHz = 12.0,
[string]$OutputRoot = (
"D:\NDC_MISSIONCORE\runtime\results\m49-tgs-integrated-graph-shadow"
)
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$TravelImageTag = "ndc/mission-core-m49-t3-travel:20260826"
$TravelImageId = "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f"
$ParityImageTag = "ndc-mission-core-m48t-upstream-parity:1.9.4-cu130"
$ParityImageId = "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0"
$RuntimeImage = (
"nvcr.io/nvidia/tritonserver:26.06-py3@" +
"sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
)
function Assert-LastExitCode([string]$Operation) {
if ($LASTEXITCODE -ne 0) { throw "$Operation failed with exit code $LASTEXITCODE" }
}
function Get-Sha256([string]$Path) {
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
function Resolve-DDirectory([string]$Path, [string]$Label, [bool]$Create) {
if ($Create -and -not (Test-Path -LiteralPath $Path)) {
$null = New-Item -ItemType Directory -Path $Path
}
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
if (
-not $item.PSIsContainer -or
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
[IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:"
) { throw "$Label must be a real D: directory" }
return $item.FullName
}
function Resolve-DFile([string]$Path, [string]$Label) {
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
if (
$item.PSIsContainer -or
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
[IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:"
) { throw "$Label must be a real D: file" }
return $item.FullName
}
function Convert-ToDockerPath([string]$Path) { return ($Path -replace "\\", "/") }
function Get-Container([string]$Name) {
$rows = @(((& docker inspect $Name) | ConvertFrom-Json))
Assert-LastExitCode "Docker inspection for $Name"
if ($rows.Count -ne 1) { throw "Container identity for $Name is not unique" }
return $rows[0]
}
function Assert-Image([string]$Tag, [string]$ExpectedId) {
$rows = @(((& docker image inspect $Tag) | ConvertFrom-Json))
Assert-LastExitCode "Docker image inspection for $Tag"
if ($rows.Count -ne 1 -or [string]$rows[0].Id -cne $ExpectedId) {
throw "Pinned image identity changed for $Tag"
}
}
function Remove-ExactContainer([string]$Name) {
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$Name$") {
& docker rm --force $Name *> $null
}
}
function Wait-Healthy([string]$Name) {
foreach ($attempt in 1..60) {
Start-Sleep -Seconds 2
$container = Get-Container $Name
if (-not $container.State.Running) {
& docker logs $Name
throw "$Name stopped during startup"
}
if ($container.State.Health.Status -ceq "healthy") { return }
}
throw "$Name did not become healthy"
}
function Wait-SharedReady(
[string]$GraphReady,
[string]$TgsReady,
[string]$GraphName,
[string]$TgsName
) {
$deadline = [DateTimeOffset]::UtcNow.AddMinutes(10)
while (-not ((Test-Path -LiteralPath $GraphReady) -and (Test-Path -LiteralPath $TgsReady))) {
foreach ($name in @($GraphName, $TgsName)) {
$container = Get-Container $name
if (-not $container.State.Running) {
& docker logs $name
throw "$name stopped before shared-start readiness"
}
}
if ([DateTimeOffset]::UtcNow -ge $deadline) {
throw "M49 integrated shared-start readiness timed out"
}
Start-Sleep -Milliseconds 250
}
}
if ($env:COMPUTERNAME -cne "DESKTOP-OPJ8J04") {
throw "M49 integrated TGS graph shadow is pinned to Worker 006"
}
$release = Resolve-DDirectory $ReleaseRoot "M49 integrated release root" $false
$payload = Resolve-DDirectory (Join-Path $release "payload") "M49 integrated payload" $false
$candidate = Resolve-DDirectory $CandidateRoot "M49 native candidate root" $false
$output = Resolve-DDirectory $OutputRoot "M49 integrated output root" $true
$runCandidate = Join-Path $output $RunId
if (Test-Path -LiteralPath $runCandidate) { throw "M49 integrated output already exists" }
$null = New-Item -ItemType Directory -Path $runCandidate
$runOutput = Resolve-DDirectory $runCandidate "M49 integrated run output" $false
foreach ($directory in @("bin", "control", "graph", "tgs")) {
$null = New-Item -ItemType Directory -Path (Join-Path $runOutput $directory)
}
$releaseDocument = Get-Content -LiteralPath (Join-Path $payload "release.json") -Raw | ConvertFrom-Json
if (
$releaseDocument.schema_version -cne "missioncore.m49-tgs-integrated-graph-worker-release/v1" -or
$releaseDocument.worker_id -cne "worker-006" -or
$releaseDocument.transition -cne "m49-tgs-native-risk-integrated-shadow/v1"
) { throw "M49 integrated release contract changed" }
foreach ($property in $releaseDocument.files.PSObject.Properties) {
$path = Join-Path $payload $property.Name
if ((Get-Sha256 $path) -cne [string]$property.Value.sha256) {
throw "M49 integrated payload digest changed: $($property.Name)"
}
}
$wheelSha256 = [string]$releaseDocument.files."nodedc_mission_core-0.1.0-py3-none-any.whl".sha256
$runnerSha256 = [string]$releaseDocument.files."run_m48s_reference_graph_shadow_worker.py".sha256
$source = [ordered]@{
CameraIndex = (
"D:\NDC_MISSIONCORE\runtime\jobs\recorded-camera-602ac89026ed12978619801d" +
"\input\camera\sensor.camera.right\epoch-1\index.jsonl"
)
SourcePack = (
"D:\NDC_MISSIONCORE\runtime\derived" +
"\e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b" +
"\lidar-pack.npz"
)
LocalSurface = (
"D:\NDC_MISSIONCORE\runtime\derived" +
"\k1-local-surface-23762244c8bdb97de26fb721ac957d7a00bc9a63571ac4cfa4be19c4effc7d55" +
"\local-surface.npz"
)
Video = (
"D:\NDC_MISSIONCORE\runtime\experiments\e46e\inputs" +
"\right-cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8.mp4"
)
Mask = (
"D:\NDC_MISSIONCORE\runtime\inputs\e2" +
"\valid-fov-mask-b4dd8ddf2b87c1d520ee8a0868c4fea062d7c14d1bae73ccabd3abe1f3acbac2" +
"\mask.png"
)
}
foreach ($entry in $source.GetEnumerator()) {
$null = Resolve-DFile $entry.Value "M49 source $($entry.Key)"
}
if ((Get-Sha256 $source.SourcePack) -cne [string]$releaseDocument.source_pack_sha256) {
throw "RAVNOVES00 source pack digest changed"
}
$nativeConfig = Resolve-DFile (
(Join-Path $payload "rf_detr_large_native_kb4_config.pbtxt")
) "native RF-DETR config"
$nativeEngine = Resolve-DFile (
(Join-Path $candidate "rf-detr-native-uint8.plan")
) "native RF-DETR engine"
if ((Get-Sha256 $nativeEngine) -cne "b8a40b3580edff001ec9680de68707242294ff590ab296000fae371f1083f695") {
throw "native RF-DETR engine SHA-256 changed"
}
$modelRoot = Join-Path $runOutput "triton-models"
$modelDirectory = Join-Path $modelRoot "rf_detr_large_native_kb4"
$modelVersionDirectory = Join-Path $modelDirectory "1"
$null = New-Item -ItemType Directory -Path $modelVersionDirectory
Copy-Item -LiteralPath $nativeConfig -Destination (Join-Path $modelDirectory "config.pbtxt")
Copy-Item -LiteralPath $nativeEngine -Destination (Join-Path $modelVersionDirectory "model.plan")
$media = Resolve-DDirectory (
"D:\NDC_MISSIONCORE\runtime\derived\perception-e15-media-pyav180-lz445-v1"
) "PyAV dependency" $false
$opencv = Resolve-DDirectory (
"D:\NDC_MISSIONCORE\runtime\derived\perception-e3-opencv413092-v1\packages"
) "OpenCV dependency" $false
$pillow = Resolve-DDirectory (
"D:\NDC_MISSIONCORE\runtime\derived\perception-p0-env-v1"
) "Pillow dependency" $false
Assert-Image $TravelImageTag $TravelImageId
Assert-Image $ParityImageTag $ParityImageId
& docker image inspect $RuntimeImage *> $null
Assert-LastExitCode "pinned runtime image inspection"
$os = Get-CimInstance Win32_OperatingSystem
$freeMemoryGiB = [double]$os.FreePhysicalMemory / 1MB
if ($freeMemoryGiB -lt 24.0) {
throw ("M49 integrated shadow requires 24 GiB free memory; observed {0:N2} GiB" -f $freeMemoryGiB)
}
$canonicalBefore = Get-Container "ndc-mission-core-triton"
if (-not $canonicalBefore.State.Running -or $canonicalBefore.State.Health.Status -cne "healthy") {
throw "Canonical Mission Core Triton must remain healthy"
}
$canonicalId = [string]$canonicalBefore.Id
$prepareName = "ndc-mission-core-m49-integrated-prepare-$RunId"
$compileName = "ndc-mission-core-m49-integrated-compile-$RunId"
$tritonName = "ndc-mission-core-m49-integrated-triton-$RunId"
$graphName = "ndc-mission-core-m49-integrated-graph-$RunId"
$tgsName = "ndc-mission-core-m49-integrated-tgs-$RunId"
$analyzeName = "ndc-mission-core-m49-integrated-analyze-$RunId"
$evidenceName = "ndc-mission-core-m49-integrated-evidence-$RunId"
$containers = @($prepareName, $compileName, $tritonName, $graphName, $tgsName, $analyzeName, $evidenceName)
foreach ($name in $containers) {
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") {
throw "M49 integrated container name already exists: $name"
}
}
$started = [DateTimeOffset]::UtcNow
try {
& docker run --rm --name $prepareName --network none --cpus 8 --memory 16g `
--entrypoint python3 `
--volume ((Convert-ToDockerPath $source.SourcePack) + ":/source/lidar-pack.npz:ro") `
--volume ((Convert-ToDockerPath $payload) + ":/release:ro") `
--volume ((Convert-ToDockerPath (Join-Path $runOutput "tgs")) + ":/tgs") `
$ParityImageTag /release/prepare_tgs_full_shadow_inputs.py `
--source-pack /source/lidar-pack.npz `
--config /release/m49-tgs-full-shadow-v1.json `
--output-root /tgs/inputs
Assert-LastExitCode "M49 integrated TGS input preparation"
& docker run --rm --name $compileName --network none --cpus 8 --memory 8g `
--entrypoint /bin/bash `
--volume ((Convert-ToDockerPath $payload) + ":/release:ro") `
--volume ((Convert-ToDockerPath (Join-Path $runOutput "bin")) + ":/out") `
$TravelImageTag /release/build_tgs_full_shadow_binary.sh /out/run_tgs_full_shadow
Assert-LastExitCode "M49 integrated TGS binary build"
& docker create --name $tritonName `
--read-only --security-opt "no-new-privileges:true" --cap-drop ALL `
--pids-limit 512 --shm-size 1g --gpus all `
--tmpfs "/tmp:rw,noexec,nosuid,size=2g" `
--health-cmd "curl --fail --silent http://127.0.0.1:8000/v2/health/ready" `
--health-interval 5s --health-timeout 3s --health-start-period 20s --health-retries 24 `
-v ((Convert-ToDockerPath $modelRoot) + ":/models:ro") `
$RuntimeImage tritonserver --model-repository=/models `
--model-control-mode=explicit --load-model=rf_detr_large_native_kb4 `
--disable-auto-complete-config --strict-readiness=true --exit-on-error=true `
--allow-http=true --allow-grpc=false --allow-metrics=false *> $null
Assert-LastExitCode "M49 integrated Triton creation"
& docker start $tritonName *> $null
Assert-LastExitCode "M49 integrated Triton start"
Wait-Healthy $tritonName
$dockerRelease = Convert-ToDockerPath $payload
$dockerRun = Convert-ToDockerPath $runOutput
$rate = [string]::Format([Globalization.CultureInfo]::InvariantCulture, "{0:R}", $SourceRateHz)
$graphArguments = @(
"create", "--name", $graphName,
"--network", ("container:{0}" -f $tritonName),
"--read-only", "--security-opt", "no-new-privileges:true", "--cap-drop", "ALL",
"--pids-limit", "256", "--gpus", "all",
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g",
"-e", "PYTHONDONTWRITEBYTECODE=1",
"-e", "PYTHONPATH=/release/nodedc_mission_core-0.1.0-py3-none-any.whl:/opt/media:/opt/opencv:/opt/pillow",
"-v", ("{0}:/release:ro" -f $dockerRelease),
"-v", ("{0}:/shared:rw" -f $dockerRun),
"-v", ((Convert-ToDockerPath $media) + ":/opt/media:ro"),
"-v", ((Convert-ToDockerPath $opencv) + ":/opt/opencv:ro"),
"-v", ((Convert-ToDockerPath $pillow) + ":/opt/pillow:ro"),
"-v", ((Convert-ToDockerPath $source.CameraIndex) + ":/source/camera-index.jsonl:ro"),
"-v", ((Convert-ToDockerPath $source.SourcePack) + ":/source/source-pack.npz:ro"),
"-v", ((Convert-ToDockerPath $source.LocalSurface) + ":/source/local-surface.npz:ro"),
"-v", ((Convert-ToDockerPath $source.Video) + ":/source/right.mp4:ro"),
"-v", ((Convert-ToDockerPath $source.Mask) + ":/source/mask.png:ro"),
"--entrypoint", "python3", $RuntimeImage,
"/release/run_m48s_reference_graph_shadow_worker.py",
"--graph-config", "/release/m48n-rf-detr-native-reference-graph-shadow-v0.json",
"--baseline-profile", "/release/m4-recorded-realtime-baseline-v1.json",
"--detector-profile", "/release/rf-detr-large-native-kb4-risk-shadow-v0.json",
"--geometry-profile", "/release/m4-geometry-association-v1.json",
"--temporal-motion-profile", "/release/m4-temporal-motion-v1.json",
"--rolling-map-profile", "/release/m4-rolling-local-map-v1.json",
"--threat-profile", "/release/m4-replay-threat-v3.json",
"--camera-index", "/source/camera-index.jsonl",
"--source-pack", "/source/source-pack.npz",
"--local-surface", "/source/local-surface.npz",
"--video", "/source/right.mp4",
"--valid-fov-mask", "/source/mask.png",
"--triton-origin", "http://127.0.0.1:8000",
"--loops", "1", "--maximum-frames", "4489", "--source-rate-hz", $rate,
"--minimum-delivery-ratio", "1.0",
"--minimum-effective-world-state-fps", "11.209069",
"--maximum-world-state-completion-p95-ms", "125.0",
"--load-purpose", "reserve-gate",
"--runtime-artifact-sha256", $wheelSha256,
"--runner-sha256", $runnerSha256,
"--shared-start-ready-file", "/shared/control/graph.ready",
"--shared-start-file", "/shared/control/start.signal",
"--output", "/shared/graph/result.json",
"--progress", "/shared/graph/progress.jsonl",
"--frame-ledger", "/shared/graph/frames.jsonl"
)
& docker @graphArguments *> $null
Assert-LastExitCode "M49 integrated graph creation"
& docker create --name $tgsName --network none --cpus 16 --memory 24g `
--read-only --security-opt "no-new-privileges:true" --cap-drop ALL `
--pids-limit 256 --tmpfs "/tmp:rw,noexec,nosuid,size=1g" `
-e ("M49_SOURCE_RATE_HZ={0}" -f $rate) `
--entrypoint /bin/bash `
--volume ($dockerRelease + ":/release:ro") `
--volume ($dockerRun + ":/shared:rw") `
$TravelImageTag /release/run_tgs_integrated_shadow.sh *> $null
Assert-LastExitCode "M49 integrated TGS creation"
& docker start $graphName *> $null
Assert-LastExitCode "M49 integrated graph start"
& docker start $tgsName *> $null
Assert-LastExitCode "M49 integrated TGS start"
$graphReady = Join-Path $runOutput "control\graph.ready"
$tgsReady = Join-Path $runOutput "control\tgs.ready"
Wait-SharedReady $graphReady $tgsReady $graphName $tgsName
[DateTimeOffset]::UtcNow.ToString("o") | Set-Content -LiteralPath (
Join-Path $runOutput "control\start.signal"
) -Encoding utf8
$telemetryPath = Join-Path $runOutput "container-telemetry.jsonl"
while ($true) {
$graphState = Get-Container $graphName
$tgsState = Get-Container $tgsName
$running = @()
if ($graphState.State.Running) { $running += $graphName }
if ($tgsState.State.Running) { $running += $tgsName }
if ((Get-Container $tritonName).State.Running) { $running += $tritonName }
if ($running.Count -gt 0) {
$stats = @((& docker stats --no-stream --format "{{json .}}" @running))
Assert-LastExitCode "M49 integrated container telemetry"
foreach ($line in $stats) {
$value = $line | ConvertFrom-Json
$role = if ($value.Name -ceq $graphName) {
"graph"
} elseif ($value.Name -ceq $tgsName) {
"tgs"
} elseif ($value.Name -ceq $tritonName) {
"triton"
} else {
throw "Unknown M49 telemetry container"
}
[ordered]@{
observed_utc = [DateTimeOffset]::UtcNow.ToString("o")
role = $role
name = [string]$value.Name
cpu_percent = [string]$value.CPUPerc
memory_usage = [string]$value.MemUsage
memory_percent = [string]$value.MemPerc
pids = [string]$value.PIDs
} | ConvertTo-Json -Compress | Out-File -LiteralPath $telemetryPath -Encoding utf8 -Append
}
}
if (-not $graphState.State.Running -and -not $tgsState.State.Running) { break }
Start-Sleep -Seconds 1
}
$graphExit = [int](Get-Container $graphName).State.ExitCode
$tgsExit = [int](Get-Container $tgsName).State.ExitCode
$previousErrorAction = $ErrorActionPreference
$ErrorActionPreference = "Continue"
$graphLogs = & docker logs $graphName 2>&1
$tgsLogs = & docker logs $tgsName 2>&1
$ErrorActionPreference = $previousErrorAction
$graphLogs | Set-Content -LiteralPath (Join-Path $runOutput "graph.log") -Encoding utf8
$tgsLogs | Set-Content -LiteralPath (Join-Path $runOutput "tgs.log") -Encoding utf8
if ($graphExit -ne 0) { throw "M49 integrated graph failed with exit code $graphExit" }
if ($tgsExit -ne 0) { throw "M49 integrated TGS failed with exit code $tgsExit" }
& docker run --rm --name $analyzeName --network none --cpus 8 --memory 16g `
--entrypoint python3 `
--volume ($dockerRelease + ":/release:ro") `
--volume ($dockerRun + ":/shared:rw") `
$ParityImageTag /release/build_tgs_full_shadow_evidence.py `
--run-root /shared/tgs `
--config /release/m49-tgs-full-shadow-v1.json `
--output-root /shared/tgs/evidence
Assert-LastExitCode "M49 integrated TGS evidence analysis"
& docker run --rm --name $evidenceName --network none --cpus 4 --memory 8g `
--entrypoint python3 `
--volume ($dockerRelease + ":/release:ro") `
--volume ($dockerRun + ":/shared:rw") `
$ParityImageTag /release/build_tgs_integrated_graph_evidence.py `
--profile /release/m49-tgs-integrated-graph-shadow-v1.json `
--graph-result /shared/graph/result.json `
--graph-frames /shared/graph/frames.jsonl `
--tgs-result /shared/tgs/evidence/result.json `
--tgs-timing /shared/tgs/tgs-full-timing.tsv `
--telemetry /shared/container-telemetry.jsonl `
--output /shared/result.json `
--release-sha256 $ExpectedArtifactSha256
Assert-LastExitCode "M49 integrated evidence gate"
} finally {
foreach ($name in $containers) { Remove-ExactContainer $name }
$canonicalAfter = Get-Container "ndc-mission-core-triton"
if (
[string]$canonicalAfter.Id -cne $canonicalId -or
-not $canonicalAfter.State.Running -or
$canonicalAfter.State.Health.Status -cne "healthy"
) { throw "Canonical Mission Core Triton changed during M49 integrated shadow" }
}
$completed = [DateTimeOffset]::UtcNow
$resultPath = Join-Path $runOutput "result.json"
if (-not (Test-Path -LiteralPath $resultPath -PathType Leaf)) {
throw "M49 integrated result is missing"
}
$result = Get-Content -LiteralPath $resultPath -Raw | ConvertFrom-Json
$summary = [ordered]@{
schema_version = "missioncore.m49-tgs-integrated-graph-worker-summary/v1"
worker_id = "worker-006"
run_id = $RunId
code_revision = [string]$releaseDocument.code_revision
source_rate_hz = $SourceRateHz
started_utc = $started.ToString("o")
completed_utc = $completed.ToString("o")
wall_seconds = [math]::Round(($completed - $started).TotalSeconds, 6)
free_memory_gib_before = [math]::Round($freeMemoryGiB, 6)
result_id = [string]$result.result_id
result_status = [string]$result.status
canonical_triton_id = $canonicalId
canonical_triton_health = "healthy"
gauss_or_playcanvas_action = "none"
durable_worker_action = "none"
navigation_or_actuation_allowed = $false
}
$summary | ConvertTo-Json -Depth 3 | Set-Content -LiteralPath (
Join-Path $runOutput "worker-summary.json"
) -Encoding utf8
$summary | ConvertTo-Json -Depth 3
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
readonly TARGET=${1:?target binary path is required}
test -f /release/run_tgs_full_shadow.cpp
test ! -e "${TARGET}"
mkdir -p "$(dirname "${TARGET}")"
g++ -std=c++17 -O3 -DNDEBUG -pthread \
-I/opt/travel/src/TRAVEL/cpp/travel/core \
-I/usr/include/eigen3 \
/release/run_tgs_full_shadow.cpp \
-o "${TARGET}"
chmod 0755 "${TARGET}"
sha256sum "${TARGET}"
@@ -0,0 +1,339 @@
#!/usr/bin/env python3
"""Seal the synchronized TGS plus native RF-DETR reference-graph shadow."""
from __future__ import annotations
import argparse
import csv
import hashlib
import json
import math
import re
from collections import defaultdict
from pathlib import Path
from typing import Any
import numpy as np
PROFILE_SCHEMA = "missioncore.m49-tgs-integrated-graph-shadow-profile/v1"
GRAPH_SCHEMA = "missioncore.m48s-reference-graph-shadow-load/v5"
TGS_SCHEMA = "missioncore.m49-tgs-full-shadow-result/v1"
RESULT_SCHEMA = "missioncore.m49-tgs-integrated-graph-shadow-result/v1"
FRAME_COUNT = 4_489
class IntegratedShadowError(RuntimeError):
"""The integrated shadow evidence is incomplete or incompatible."""
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def canonical_json(value: object) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
def load_json(path: Path, label: str) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8-sig"))
except (OSError, json.JSONDecodeError) as exc:
raise IntegratedShadowError(f"{label} is unreadable") from exc
if not isinstance(value, dict):
raise IntegratedShadowError(f"{label} is not an object")
return value
def distribution(values: list[float]) -> dict[str, float]:
if not values:
return {"mean": 0.0, "p50": 0.0, "p95": 0.0, "p99": 0.0, "maximum": 0.0}
array = np.asarray(values, dtype=np.float64)
return {
"mean": round(float(array.mean()), 6),
"p50": round(float(np.percentile(array, 50)), 6),
"p95": round(float(np.percentile(array, 95)), 6),
"p99": round(float(np.percentile(array, 99)), 6),
"maximum": round(float(array.max()), 6),
}
def graph_completion_ages(path: Path) -> list[float]:
ages: list[float] = []
with path.open("r", encoding="utf-8") as stream:
for expected, line in enumerate(stream):
row = json.loads(line)
sequence = row.get("source_envelope", {}).get("sequence")
if sequence != expected:
raise IntegratedShadowError("graph frame ledger sequence changed")
age_ns = row.get("completion_age_ns")
if not isinstance(age_ns, int) or age_ns < 0:
raise IntegratedShadowError("graph completion age is invalid")
ages.append(age_ns / 1_000_000.0)
if len(ages) != FRAME_COUNT:
raise IntegratedShadowError("graph frame ledger is incomplete")
return ages
def tgs_completion_ages(path: Path) -> list[float]:
ages: list[float] = []
with path.open("r", encoding="utf-8", newline="") as stream:
for expected, row in enumerate(csv.DictReader(stream, delimiter="\t")):
if int(row["timeline_frame_index"]) != expected:
raise IntegratedShadowError("TGS timing sequence changed")
value = float(row["completion_age_ms"])
if not math.isfinite(value) or value < 0:
raise IntegratedShadowError("TGS completion age is invalid")
ages.append(value)
if len(ages) != FRAME_COUNT:
raise IntegratedShadowError("TGS timing ledger is incomplete")
return ages
_SIZE = re.compile(r"^\s*([0-9.]+)\s*([kmgt]?i?b)\s*$", re.IGNORECASE)
def size_mib(value: str) -> float:
match = _SIZE.fullmatch(value)
if match is None:
raise IntegratedShadowError("container memory telemetry is invalid")
number = float(match.group(1))
unit = match.group(2).lower()
scale = {
"b": 1.0 / (1024.0 * 1024.0),
"kb": 1.0 / 1024.0,
"kib": 1.0 / 1024.0,
"mb": 1.0,
"mib": 1.0,
"gb": 1024.0,
"gib": 1024.0,
"tb": 1024.0 * 1024.0,
"tib": 1024.0 * 1024.0,
}[unit]
return number * scale
def host_telemetry(path: Path) -> dict[str, object]:
samples: dict[str, list[dict[str, float]]] = defaultdict(list)
with path.open("r", encoding="utf-8-sig") as stream:
for line in stream:
row = json.loads(line)
role = row.get("role")
if role not in {"graph", "tgs", "triton"}:
raise IntegratedShadowError("container telemetry role changed")
cpu_text = row.get("cpu_percent")
memory_text = row.get("memory_usage")
memory_percent_text = row.get("memory_percent")
if not all(
isinstance(value, str) for value in (cpu_text, memory_text, memory_percent_text)
):
raise IntegratedShadowError("container telemetry row is incomplete")
used_text = memory_text.split("/", 1)[0].strip()
samples[role].append(
{
"cpu_percent": float(cpu_text.rstrip("%")),
"memory_used_mib": size_mib(used_text),
"memory_percent": float(memory_percent_text.rstrip("%")),
}
)
if any(not samples[role] for role in ("graph", "tgs", "triton")):
raise IntegratedShadowError("container telemetry does not cover every runtime role")
return {
role: {
"sample_count": len(rows),
"cpu_percent": distribution([row["cpu_percent"] for row in rows]),
"memory_used_mib": distribution([row["memory_used_mib"] for row in rows]),
"memory_percent": distribution([row["memory_percent"] for row in rows]),
}
for role, rows in sorted(samples.items())
}
def build(
*,
profile_path: Path,
graph_result_path: Path,
graph_frames_path: Path,
tgs_result_path: Path,
tgs_timing_path: Path,
telemetry_path: Path,
output_path: Path,
release_sha256: str,
) -> dict[str, object]:
if output_path.exists():
raise IntegratedShadowError("integrated result already exists")
profile = load_json(profile_path, "integrated profile")
graph = load_json(graph_result_path, "reference graph result")
tgs = load_json(tgs_result_path, "TGS result")
if profile.get("schema_version") != PROFILE_SCHEMA:
raise IntegratedShadowError("integrated profile schema changed")
if graph.get("schema_version") != GRAPH_SCHEMA:
raise IntegratedShadowError("reference graph result schema changed")
if tgs.get("schema_version") != TGS_SCHEMA:
raise IntegratedShadowError("TGS result schema changed")
if len(release_sha256) != 64 or any(
value not in "0123456789abcdef" for value in release_sha256
):
raise IntegratedShadowError("release SHA-256 is invalid")
graph_ages = graph_completion_ages(graph_frames_path)
tgs_ages = tgs_completion_ages(tgs_timing_path)
combined_ages = [
max(graph_age, tgs_age) for graph_age, tgs_age in zip(graph_ages, tgs_ages, strict=True)
]
combined = distribution(combined_ages)
telemetry = host_telemetry(telemetry_path)
acceptance_profile = profile["acceptance"]
execution = graph.get("execution", {})
graph_metrics = graph.get("metrics", {})
tgs_performance = tgs.get("performance", {})
effective_fps = float(execution.get("effective_world_state_fps", 0.0))
reference_fps = float(acceptance_profile["reference_world_state_fps"])
fps_regression = max(0.0, (reference_fps - effective_fps) / reference_fps)
graph_p95 = float(graph_metrics.get("world_state_completion_age_ms", {}).get("p95", math.inf))
tgs_p95 = float(tgs_performance.get("candidate_tgs_ms", {}).get("p95", math.inf))
tgs_p99 = float(tgs_performance.get("candidate_tgs_ms", {}).get("p99", math.inf))
tgs_drops = int(tgs_performance.get("capacity_drop_count", -1))
terminal = execution.get("terminal_outcomes", {})
superseded = int(terminal.get("superseded", 0)) if isinstance(terminal, dict) else -1
gpu_samples = int(graph_metrics.get("gpu", {}).get("sample_count", 0))
graph_inputs = graph.get("identity", {}).get("inputs", {})
checks = {
"frozen_graph_identity": (
graph_inputs.get("graph_config")
== profile["stages"]["reference_graph"]["graph_config_sha256"]
and graph_inputs.get("detector_profile")
== profile["stages"]["reference_graph"]["detector_profile_sha256"]
),
"frozen_tgs_identity": (
tgs.get("config_sha256") == profile["stages"]["tgs"]["profile_sha256"]
),
"requested_source_rate_preserved": (
execution.get("requested_source_rate_hz")
== profile["source"]["requested_source_rate_hz"]
),
"all_graph_frames_delivered": (
execution.get("admitted_frames") == FRAME_COUNT
and execution.get("delivered_world_states") == FRAME_COUNT
),
"all_tgs_frames_accounted": tgs.get("timeline", {}).get("frame_count") == FRAME_COUNT,
"exact_sequence_join": len(combined_ages) == FRAME_COUNT,
"minimum_delivery_ratio": float(execution.get("delivery_ratio", 0.0))
>= float(acceptance_profile["minimum_delivery_ratio"]),
"maximum_world_state_fps_regression": fps_regression
<= float(acceptance_profile["maximum_world_state_fps_regression_fraction"]),
"minimum_effective_world_state_fps": effective_fps
>= float(acceptance_profile["minimum_effective_world_state_fps"]),
"maximum_world_state_completion_p95_ms": graph_p95
<= float(acceptance_profile["maximum_world_state_completion_p95_ms"]),
"candidate_stage_p95_ms": tgs_p95
<= float(acceptance_profile["candidate_stage_p95_ms_max"]),
"candidate_stage_p99_ms": tgs_p99
<= float(acceptance_profile["candidate_stage_p99_ms_max"]),
"combined_output_age_p99_ms": combined["p99"]
<= float(acceptance_profile["combined_output_age_p99_ms_max"]),
"zero_capacity_drops": tgs_drops <= int(acceptance_profile["capacity_drop_count_max"])
and superseded <= int(acceptance_profile["capacity_drop_count_max"]),
"reference_graph_integrity": graph.get("evidence_integrity_gate_passed") is True,
"tgs_integrity": tgs.get("status") == "passed",
"host_resource_telemetry_complete": all(
telemetry[role]["sample_count"] > 0 for role in ("graph", "tgs", "triton")
),
"gpu_telemetry_complete": gpu_samples > 0,
"authority_remains_false": all(value is False for value in profile["authority"].values()),
}
files = {
label: {"bytes": path.stat().st_size, "sha256": sha256_file(path)}
for label, path in (
("graph-result.json", graph_result_path),
("graph-frames.jsonl", graph_frames_path),
("tgs-result.json", tgs_result_path),
("tgs-timing.tsv", tgs_timing_path),
("container-telemetry.jsonl", telemetry_path),
)
}
document: dict[str, object] = {
"schema_version": RESULT_SCHEMA,
"profile_id": profile["profile_id"],
"status": "passed" if all(checks.values()) else "failed",
"source": {
"source_id": profile["source"]["source_id"],
"source_pack_sha256": profile["source"]["source_pack_sha256"],
"requested_source_rate_hz": profile["source"]["requested_source_rate_hz"],
"joined_frame_count": len(combined_ages),
},
"identity": {
"release_sha256": release_sha256,
"profile_sha256": sha256_file(profile_path),
"graph_config_sha256": profile["stages"]["reference_graph"]["graph_config_sha256"],
"tgs_profile_sha256": profile["stages"]["tgs"]["profile_sha256"],
"linked_accepted_tgs_result_id": profile["stages"]["tgs"]["linked_accepted_result_id"],
},
"performance": {
"effective_world_state_fps": effective_fps,
"reference_world_state_fps": reference_fps,
"world_state_fps_regression_fraction": round(fps_regression, 9),
"world_state_completion_age_ms": graph_metrics.get("world_state_completion_age_ms"),
"tgs_candidate_stage_ms": tgs_performance.get("candidate_tgs_ms"),
"tgs_completion_age_ms": tgs_performance.get("completion_age_ms"),
"combined_output_age_ms": combined,
"gpu": graph_metrics.get("gpu"),
"host_containers": telemetry,
},
"accounting": {
"graph_admitted": execution.get("admitted_frames"),
"graph_delivered": execution.get("delivered_world_states"),
"graph_terminal_outcomes": terminal,
"tgs_timeline_frames": tgs.get("timeline", {}).get("frame_count"),
"tgs_available_lidar_frames": tgs.get("timeline", {}).get(
"available_lidar_frame_count"
),
"tgs_capacity_drops": tgs_drops,
},
"checks": checks,
"integrated_runtime_gate_passed": all(checks.values()),
"visual_quality_accepted": False,
"traversability_accepted": False,
"production_accepted": False,
"authority": profile["authority"],
"files": files,
}
identity = hashlib.sha256(canonical_json(document)).hexdigest()
document["result_id"] = f"m49-tgs-integrated-graph-shadow-{identity}"
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return document
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--profile", type=Path, required=True)
parser.add_argument("--graph-result", type=Path, required=True)
parser.add_argument("--graph-frames", type=Path, required=True)
parser.add_argument("--tgs-result", type=Path, required=True)
parser.add_argument("--tgs-timing", type=Path, required=True)
parser.add_argument("--telemetry", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
parser.add_argument("--release-sha256", required=True)
arguments = parser.parse_args()
result = build(
profile_path=arguments.profile,
graph_result_path=arguments.graph_result,
graph_frames_path=arguments.graph_frames,
tgs_result_path=arguments.tgs_result,
tgs_timing_path=arguments.tgs_timing,
telemetry_path=arguments.telemetry,
output_path=arguments.output,
release_sha256=arguments.release_sha256,
)
print(
json.dumps({"result_id": result["result_id"], "status": result["status"]}, sort_keys=True)
)
return 0 if result["status"] == "passed" else 2
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,4 +1,5 @@
#include <chrono> #include <chrono>
#include <cmath>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
@@ -76,11 +77,38 @@ double milliseconds(Clock::duration duration) {
return std::chrono::duration<double, std::milli>(duration).count(); return std::chrono::duration<double, std::milli>(duration).count();
} }
void waitForSharedStart(const std::string& ready_path, const std::string& start_path) {
if (ready_path.empty() != start_path.empty()) {
throw std::runtime_error("shared-start paths must be configured together");
}
if (ready_path.empty()) {
return;
}
if (std::filesystem::exists(ready_path)) {
throw std::runtime_error("shared-start ready file already exists");
}
{
std::ofstream ready(ready_path);
ready << "ready\n";
if (!ready) {
throw std::runtime_error("cannot publish TGS shared-start readiness");
}
}
const auto deadline = Clock::now() + std::chrono::minutes(10);
while (!std::filesystem::is_regular_file(start_path)) {
if (Clock::now() >= deadline) {
throw std::runtime_error("TGS shared-start barrier timed out");
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
} // namespace } // namespace
int main(int argc, char** argv) { int main(int argc, char** argv) {
if (argc != 5) { if (argc != 5 && argc != 8) {
std::cerr << "Usage: run_tgs_full_shadow <sequence_dir> <schedule.tsv> <output_dir> <timing.tsv>\n"; std::cerr << "Usage: run_tgs_full_shadow <sequence_dir> <schedule.tsv> <output_dir>"
" <timing.tsv> [target_rate_hz ready_file start_file]\n";
return 1; return 1;
} }
try { try {
@@ -89,6 +117,19 @@ int main(int argc, char** argv) {
const std::string output_dir = argv[3]; const std::string output_dir = argv[3];
const std::string timing_path = argv[4]; const std::string timing_path = argv[4];
const auto schedule = readSchedule(schedule_path); const auto schedule = readSchedule(schedule_path);
const double target_rate_hz = argc == 8 ? std::stod(argv[5]) : 0.0;
if (target_rate_hz < 0.0 || !std::isfinite(target_rate_hz)) {
throw std::runtime_error("invalid TGS target rate");
}
const double source_duration_seconds =
schedule.back().session_seconds - schedule.front().session_seconds;
if (!(source_duration_seconds > 0.0)) {
throw std::runtime_error("invalid TGS source duration");
}
const double recorded_rate_hz =
static_cast<double>(schedule.size() - 1) / source_duration_seconds;
const double pacing_scale =
target_rate_hz > 0.0 ? recorded_rate_hz / target_rate_hz : 1.0;
KittiLoader loader(sequence_dir); KittiLoader loader(sequence_dir);
if (loader.size() != 3928) { if (loader.size() != 3928) {
throw std::runtime_error("full-shadow available LiDAR frame count changed"); throw std::runtime_error("full-shadow available LiDAR frame count changed");
@@ -103,12 +144,14 @@ int main(int argc, char** argv) {
<< "\ttgs_ms\tstage_wall_ms\tqueue_delay_ms\tcompletion_age_ms\tcapacity_drop\n"; << "\ttgs_ms\tstage_wall_ms\tqueue_delay_ms\tcompletion_age_ms\tcapacity_drop\n";
timing << std::fixed << std::setprecision(6); timing << std::fixed << std::setprecision(6);
waitForSharedStart(argc == 8 ? argv[6] : "", argc == 8 ? argv[7] : "");
const double first_source_seconds = schedule.front().session_seconds; const double first_source_seconds = schedule.front().session_seconds;
const auto run_started = Clock::now(); const auto run_started = Clock::now();
std::size_t expected_slot = 0; std::size_t expected_slot = 0;
for (const auto& row : schedule) { for (const auto& row : schedule) {
const auto target = run_started + std::chrono::duration_cast<Clock::duration>( const auto target = run_started + std::chrono::duration_cast<Clock::duration>(
std::chrono::duration<double>(row.session_seconds - first_source_seconds)); std::chrono::duration<double>(
(row.session_seconds - first_source_seconds) * pacing_scale));
const auto before_wait = Clock::now(); const auto before_wait = Clock::now();
if (before_wait < target) { if (before_wait < target) {
std::this_thread::sleep_until(target); std::this_thread::sleep_until(target);
@@ -173,6 +216,9 @@ int main(int argc, char** argv) {
throw std::runtime_error("full-shadow available frame accounting changed"); throw std::runtime_error("full-shadow available frame accounting changed");
} }
std::cout << "[TGS-FULL] complete timeline=4489 available=3928\n"; std::cout << "[TGS-FULL] complete timeline=4489 available=3928\n";
std::cout << "[TGS-FULL] recorded_rate_hz=" << recorded_rate_hz
<< " target_rate_hz=" << (target_rate_hz > 0.0 ? target_rate_hz : recorded_rate_hz)
<< "\n";
return 0; return 0;
} catch (const std::exception& error) { } catch (const std::exception& error) {
std::cerr << "[TGS-FULL] " << error.what() << '\n'; std::cerr << "[TGS-FULL] " << error.what() << '\n';
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail
readonly BINARY=/shared/bin/run_tgs_full_shadow
readonly INPUT_ROOT=/shared/tgs/inputs
readonly OUTPUT_ROOT=/shared/tgs/outputs/causal_rolling_1s
readonly TIMING_PATH=/shared/tgs/tgs-full-timing.tsv
readonly READY_FILE=/shared/control/tgs.ready
readonly START_FILE=/shared/control/start.signal
readonly SOURCE_RATE_HZ=${M49_SOURCE_RATE_HZ:-12.0}
test -x "${BINARY}"
test -f "${INPUT_ROOT}/input-manifest.json"
test -f "${INPUT_ROOT}/schedule.tsv"
test ! -e /shared/tgs/outputs
test ! -e "${TIMING_PATH}"
test ! -e "${READY_FILE}"
mkdir -p "${OUTPUT_ROOT}"
exec /usr/bin/time -v "${BINARY}" \
"${INPUT_ROOT}/profiles/causal_rolling_1s" \
"${INPUT_ROOT}/schedule.tsv" \
"${OUTPUT_ROOT}" \
"${TIMING_PATH}" \
"${SOURCE_RATE_HZ}" \
"${READY_FILE}" \
"${START_FILE}"
@@ -0,0 +1,258 @@
#!/usr/bin/env python3
"""Build a clean-revision Worker 006 release for the integrated M4.9 shadow."""
from __future__ import annotations
import argparse
import gzip
import hashlib
import io
import json
import os
import re
import subprocess
import tarfile
import tempfile
from pathlib import Path
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
WHEEL_NAME = "nodedc_mission_core-0.1.0-py3-none-any.whl"
PATCH_ID = re.compile(r"^[A-Za-z0-9._-]{1,96}$")
SOURCES = (
Path("experiments/perception/worker/Invoke-M49TgsIntegratedGraphShadow.ps1"),
Path("experiments/perception/run_m48s_reference_graph_shadow_worker.py"),
Path("experiments/perception/worker/rf_detr_large_native_kb4_config.pbtxt"),
Path("experiments/perception/worker/m49_t3_travel/prepare_tgs_fail_closed_inputs.py"),
Path("experiments/perception/worker/m49_t3_travel/build_tgs_fail_closed_evidence.py"),
Path("experiments/perception/worker/m49_t3_travel/prepare_tgs_full_shadow_inputs.py"),
Path("experiments/perception/worker/m49_t3_travel/run_tgs_full_shadow.cpp"),
Path("experiments/perception/worker/m49_t3_travel/build_tgs_full_shadow_binary.sh"),
Path("experiments/perception/worker/m49_t3_travel/run_tgs_integrated_shadow.sh"),
Path("experiments/perception/worker/m49_t3_travel/build_tgs_full_shadow_evidence.py"),
Path("experiments/perception/worker/m49_t3_travel/build_tgs_integrated_graph_evidence.py"),
Path("config/perception/m49-tgs-integrated-graph-shadow-v1.json"),
Path("config/perception/m49-tgs-full-shadow-v1.json"),
Path("config/perception/m48n-rf-detr-native-reference-graph-shadow-v0.json"),
Path("config/perception/m4-recorded-realtime-baseline-v1.json"),
Path("config/perception/rf-detr-large-native-kb4-risk-shadow-v0.json"),
Path("config/perception/m4-geometry-association-v1.json"),
Path("config/perception/m4-temporal-motion-v1.json"),
Path("config/perception/m4-rolling-local-map-v1.json"),
Path("config/perception/m4-replay-threat-v3.json"),
)
class ArtifactBuildError(RuntimeError):
"""The integrated Worker release cannot be built from its declared revision."""
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def git_revision() -> str:
result = subprocess.run(
["git", "rev-parse", "HEAD"],
cwd=REPOSITORY_ROOT,
check=True,
capture_output=True,
text=True,
)
revision = result.stdout.strip()
if re.fullmatch(r"[a-f0-9]{40}", revision) is None:
raise ArtifactBuildError("Git revision is not a full SHA-1")
return revision
def materialize_revision(revision: str, destination: Path) -> None:
archive_path = destination.parent / "source.tar"
subprocess.run(
["git", "archive", "--format=tar", "--output", str(archive_path), revision],
cwd=REPOSITORY_ROOT,
check=True,
capture_output=True,
)
destination.mkdir()
root = destination.resolve()
with tarfile.open(archive_path, "r:") as archive:
for member in archive.getmembers():
target = (destination / member.name).resolve()
if target != root and root not in target.parents:
raise ArtifactBuildError("Git archive contains an unsafe path")
archive.extractall(destination)
def build_wheel(source_root: Path, output: Path) -> Path:
environment = os.environ.copy()
environment["SOURCE_DATE_EPOCH"] = "0"
result = subprocess.run(
["uv", "build", "--wheel", "--out-dir", str(output)],
cwd=source_root,
env=environment,
check=False,
capture_output=True,
text=True,
)
if result.returncode != 0:
detail = (result.stderr or result.stdout).strip()
raise ArtifactBuildError(f"wheel build failed: {detail}")
wheel = output / WHEEL_NAME
if not wheel.is_file() or wheel.is_symlink():
raise ArtifactBuildError("expected Worker wheel was not built")
return wheel
def tar_info(path: Path, arcname: str) -> tarfile.TarInfo:
info = tarfile.TarInfo(arcname)
info.uid = info.gid = 0
info.uname = info.gname = "root"
info.mtime = 0
if path.is_dir():
info.type = tarfile.DIRTYPE
info.mode = 0o755
else:
info.type = tarfile.REGTYPE
info.mode = 0o755 if path.suffix in {".sh", ".ps1", ".py"} else 0o644
info.size = path.stat().st_size
return info
def write_archive(stage: Path, target: Path) -> None:
members = [stage / "manifest.env", stage / "files.txt", stage / "payload"]
members.extend(sorted((stage / "payload").rglob("*")))
target.parent.mkdir(parents=True, exist_ok=True)
with (
target.open("wb") as raw,
gzip.GzipFile(filename="", mode="wb", fileobj=raw, compresslevel=9, mtime=0) as compressed,
tarfile.open(fileobj=compressed, mode="w", format=tarfile.PAX_FORMAT) as archive,
):
for path in members:
info = tar_info(path, path.relative_to(stage).as_posix())
if path.is_file():
with path.open("rb") as stream:
archive.addfile(info, stream)
else:
archive.addfile(info, io.BytesIO())
def build_artifact(
patch_id: str,
output_directory: Path,
*,
revision: str | None = None,
source_root: Path | None = None,
) -> dict[str, object]:
if PATCH_ID.fullmatch(patch_id) is None:
raise ArtifactBuildError("patch id is invalid")
selected_revision = revision or git_revision()
if re.fullmatch(r"[a-f0-9]{40}", selected_revision) is None:
raise ArtifactBuildError("artifact revision is invalid")
with tempfile.TemporaryDirectory(prefix="mission-core-m49-integrated-") as directory:
stage = Path(directory)
snapshot = source_root
if snapshot is None:
snapshot = stage / "source"
materialize_revision(selected_revision, snapshot)
sources = tuple(snapshot / relative for relative in SOURCES)
if any(path.is_symlink() or not path.is_file() for path in sources):
raise ArtifactBuildError("release input is not a regular file")
payload = stage / "payload"
payload.mkdir()
wheel = build_wheel(snapshot, stage / "wheel")
copied: list[Path] = []
for source in sources:
destination = payload / source.name
if destination.exists():
raise ArtifactBuildError("release payload file names are not unique")
destination.write_bytes(source.read_bytes())
copied.append(destination)
wheel_destination = payload / WHEEL_NAME
wheel_destination.write_bytes(wheel.read_bytes())
copied.append(wheel_destination)
release = {
"schema_version": "missioncore.m49-tgs-integrated-graph-worker-release/v1",
"patch_id": patch_id,
"transition": "m49-tgs-native-risk-integrated-shadow/v1",
"code_revision": selected_revision,
"worker_id": "worker-006",
"source_pack_sha256": (
"0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944"
),
"expected_frames": 4489,
"requested_source_rate_hz": 12.0,
"native_engine_sha256": (
"b8a40b3580edff001ec9680de68707242294ff590ab296000fae371f1083f695"
),
"images": {
"travel": "sha256:7b412020f4d8392d1d1ed1b33beadc44140f0ea8f781e62dd69796042334300f",
"parity": "sha256:ceb13548617e4bd3f619766bfdff00af3fa5160946b367828da6d2233dcdcba0",
"runtime": (
"sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
),
},
"authority": {
"visual_quality_accepted": False,
"traversability_accepted": False,
"physical_free_space_accepted": False,
"commands_enabled": False,
"actuation_allowed": False,
"navigation_or_safety_accepted": False,
"production_accepted": False,
},
"scope": {
"gauss_or_playcanvas_action": "none",
"durable_worker_action": "none",
"canonical_triton_action": "none",
},
"files": {
path.name: {"sha256": sha256_file(path), "bytes": path.stat().st_size}
for path in sorted(copied)
},
}
release_path = payload / "release.json"
release_path.write_text(
json.dumps(release, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
payload_files = sorted((*release["files"], release_path.name))
(stage / "manifest.env").write_text(
f"id={patch_id}\ncomponent=mission-core-worker\ntype=shadow-release\n",
encoding="utf-8",
)
(stage / "files.txt").write_text("\n".join(payload_files) + "\n", encoding="utf-8")
target = output_directory.resolve() / f"nodedc-{patch_id}.tgz"
write_archive(stage, target)
return {
"ok": True,
"patch_id": patch_id,
"artifact": str(target),
"sha256": sha256_file(target),
"code_revision": selected_revision,
"wheel_sha256": release["files"][WHEEL_NAME]["sha256"],
"payload_files": payload_files,
"transition": release["transition"],
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("patch_id")
parser.add_argument(
"--output-directory",
type=Path,
default=REPOSITORY_ROOT / ".runtime/worker-artifacts",
)
arguments = parser.parse_args()
try:
result = build_artifact(arguments.patch_id, arguments.output_directory)
except (ArtifactBuildError, OSError, subprocess.SubprocessError) as exc:
parser.error(str(exc))
print(json.dumps(result, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+21
View File
@@ -344,6 +344,27 @@ class RecordedGeometryStore:
points.setflags(write=False) points.setflags(write=False)
return points return points
def playback_points_map(self) -> FloatArray:
"""Expose the sealed contiguous map-point track for binary LAB playback.
The returned array is the exact source-pack point index space. It is
read-only and deliberately excludes any UI projection or resampling so
the browser can retain it once and derive the current increment by the
verified offsets below.
"""
points = np.asarray(self._source["cloud_points_map"], dtype=np.dtype("<f4"))
if not points.flags.c_contiguous:
raise GeometryProviderError("source playback point track is not contiguous")
points.setflags(write=False)
return points
def playback_point_offsets(self) -> tuple[int, ...]:
"""Return immutable offsets into :meth:`playback_points_map`."""
offsets = np.asarray(self._source["cloud_offsets"], dtype=np.int64)
return tuple(int(value) for value in offsets)
def point_step_candidates_for_frame(self, frame_index: int) -> UInt8Array | None: def point_step_candidates_for_frame(self, frame_index: int) -> UInt8Array | None:
"""Expose the sealed low-step diagnostic in the source point index space. """Expose the sealed low-step diagnostic in the source point index space.
+28 -12
View File
@@ -150,14 +150,23 @@ class RecordedThreatTimeline:
"access": "read-only-bounded-recorded-replay", "access": "read-only-bounded-recorded-replay",
} }
def chunk(self, *, start_sequence: int, frame_count: int) -> dict[str, object]: def chunk(
self,
*,
start_sequence: int,
frame_count: int,
include_points: bool = True,
) -> dict[str, object]:
if not 0 <= start_sequence < len(self.index.offsets): if not 0 <= start_sequence < len(self.index.offsets):
raise RecordedThreatTimelineError("recorded timeline chunk start is invalid") raise RecordedThreatTimelineError("recorded timeline chunk start is invalid")
if not 1 <= frame_count <= RECORDED_SPATIAL_MAX_CHUNK_FRAMES: if not 1 <= frame_count <= RECORDED_SPATIAL_MAX_CHUNK_FRAMES:
raise RecordedThreatTimelineError("recorded timeline chunk size is invalid") raise RecordedThreatTimelineError("recorded timeline chunk size is invalid")
stop = min(len(self.index.offsets), start_sequence + frame_count) stop = min(len(self.index.offsets), start_sequence + frame_count)
with self._lock: with self._lock:
frames = [self._project_frame(sequence) for sequence in range(start_sequence, stop)] frames = [
self._project_frame(sequence, include_points=include_points)
for sequence in range(start_sequence, stop)
]
return { return {
"schema_version": RECORDED_SPATIAL_CHUNK_SCHEMA, "schema_version": RECORDED_SPATIAL_CHUNK_SCHEMA,
"result_id": self.result.result_id, "result_id": self.result.result_id,
@@ -170,7 +179,12 @@ class RecordedThreatTimeline:
"access": "read-only-bounded-recorded-replay", "access": "read-only-bounded-recorded-replay",
} }
def _project_frame(self, sequence: int) -> dict[str, object]: def _project_frame(
self,
sequence: int,
*,
include_points: bool,
) -> dict[str, object]:
row = _read_frame_at(self.frames_path, self.index, sequence) row = _read_frame_at(self.frames_path, self.index, sequence)
frame_id = row.get("frame_id") frame_id = row.get("frame_id")
if not isinstance(frame_id, str) or not frame_id: if not isinstance(frame_id, str) or not frame_id:
@@ -193,11 +207,13 @@ class RecordedThreatTimeline:
raise RecordedThreatTimelineError( raise RecordedThreatTimelineError(
"recorded timeline current increment binding changed" "recorded timeline current increment binding changed"
) )
point_cloud, point_source_count = sample_points_in_body_frame( point_source_count = int(points.shape[0])
points, if include_points:
body_frame, point_cloud, point_source_count = sample_points_in_body_frame(
point_limit=RECORDED_SPATIAL_POINT_LIMIT, points,
) body_frame,
point_limit=RECORDED_SPATIAL_POINT_LIMIT,
)
metric_visuals = project_metric_obstacles_to_body( metric_visuals = project_metric_obstacles_to_body(
_mapping_array(row.get("metric_obstacles"), "metric obstacles"), _mapping_array(row.get("metric_obstacles"), "metric obstacles"),
body_frame, body_frame,
@@ -219,13 +235,13 @@ class RecordedThreatTimeline:
if body_frame is None if body_frame is None
else { else {
"origin_map_xyz_m": list(body_frame.origin_map_xyz_m), "origin_map_xyz_m": list(body_frame.origin_map_xyz_m),
"basis_map_from_body": [ "basis_map_from_body": [list(row) for row in body_frame.basis_map_from_body],
list(row) for row in body_frame.basis_map_from_body
],
}, },
"point_cloud_body_xyz_m": point_cloud, "point_cloud_body_xyz_m": point_cloud,
"point_cloud_source_count": point_source_count, "point_cloud_source_count": point_source_count,
"point_cloud_sample_count": len(point_cloud), "point_cloud_sample_count": point_source_count
if not include_points
else len(point_cloud),
"point_cloud_layer": "current-increment", "point_cloud_layer": "current-increment",
"rolling_map_component_count": sum( "rolling_map_component_count": sum(
item.get("state") == "retained" for item in metric_visuals item.get("state") == "retained" for item in metric_visuals
+125 -10
View File
@@ -13,17 +13,44 @@ from typing import Final
import numpy as np import numpy as np
from fastapi import APIRouter, HTTPException, Query, Response from fastapi import APIRouter, HTTPException, Query, Response
from fastapi.responses import FileResponse
from k1link.laboratory.m49_tgs_full_shadow import ( from k1link.laboratory.m49_tgs_full_shadow import (
PREFIX,
M49TgsFullShadowError, M49TgsFullShadowError,
M49TgsFullShadowResult, M49TgsFullShadowResult,
PREFIX,
read_m49_tgs_full_shadow, read_m49_tgs_full_shadow,
) )
RootProvider = Callable[[], Path | None] RootProvider = Callable[[], Path | None]
RESULT_ID: Final = re.compile(rf"^{re.escape(PREFIX)}[a-f0-9]{{64}}$") RESULT_ID: Final = re.compile(rf"^{re.escape(PREFIX)}[a-f0-9]{{64}}$")
ENDPOINT_ROOT: Final = "/api/v1/laboratory/m49/tgs-full-shadow" ENDPOINT_ROOT: Final = "/api/v1/laboratory/m49/tgs-full-shadow"
PLAYBACK_TRACKS: Final = {
"centers": (
"costmap-cell-centers-xy-m.npy",
"application/x-npy",
"<f4",
[2244, 2],
),
"states": (
"costmap-states.npy",
"application/x-npy",
"|u1",
[4489, 2244],
),
"z-bounds": (
"costmap-z-bounds-m.npy",
"application/x-npy",
"<f4",
[4489, 2244, 2],
),
"frames": (
"frames.ndjson",
"application/x-ndjson",
"ndjson",
[4489],
),
}
def build_m49_tgs_full_shadow_router(*, root_provider: RootProvider = lambda: None) -> APIRouter: def build_m49_tgs_full_shadow_router(*, root_provider: RootProvider = lambda: None) -> APIRouter:
@@ -55,16 +82,46 @@ def build_m49_tgs_full_shadow_router(*, root_provider: RootProvider = lambda: No
if not candidate.is_dir() or RESULT_ID.fullmatch(candidate.name) is None: if not candidate.is_dir() or RESULT_ID.fullmatch(candidate.name) is None:
continue continue
try: try:
results.append(_project(_read_cached(str(candidate.resolve()), _signature(candidate)))) results.append(
_project(_read_cached(str(candidate.resolve()), _signature(candidate)))
)
except (M49TgsFullShadowError, OSError, ValueError): except (M49TgsFullShadowError, OSError, ValueError):
invalid += 1 invalid += 1
results.sort(key=lambda row: (str(row["created_at_utc"]), str(row["result_id"])), reverse=True) results.sort(
key=lambda row: (str(row["created_at_utc"]), str(row["result_id"])), reverse=True
)
return _catalog(results[:limit], configured=True, invalid_total=invalid) return _catalog(results[:limit], configured=True, invalid_total=invalid)
@router.get("/{result_id}") @router.get("/{result_id}")
def get_result(result_id: str) -> dict[str, object]: def get_result(result_id: str) -> dict[str, object]:
return _project(sealed(result_id)) return _project(sealed(result_id))
@router.get("/{result_id}/playback/manifest")
def get_playback_manifest(result_id: str) -> dict[str, object]:
return _playback_manifest(sealed(result_id))
@router.get("/{result_id}/playback/tracks/{track_id}")
def get_playback_track(result_id: str, track_id: str) -> FileResponse:
result = sealed(result_id)
descriptor = PLAYBACK_TRACKS.get(track_id)
if descriptor is None:
raise HTTPException(status_code=404, detail="M49 playback track not found")
name, media_type, _dtype, _shape = descriptor
artifact = _artifact_descriptor(result, name)
return FileResponse(
result.root / name,
media_type=media_type,
headers={
"Cache-Control": "private, max-age=31536000, immutable",
# These playback artifacts are consumed on the local control
# station. Avoid spending multiple seconds recompressing
# already compact numeric tracks on every cold open.
"Content-Encoding": "identity",
"ETag": f'"{artifact["sha256"]}"',
"X-Content-Type-Options": "nosniff",
},
)
@router.get("/{result_id}/frames/{source_sequence}/spatial") @router.get("/{result_id}/frames/{source_sequence}/spatial")
def get_spatial(result_id: str, source_sequence: int) -> Response: def get_spatial(result_id: str, source_sequence: int) -> Response:
if source_sequence < 0 or source_sequence >= 4489: if source_sequence < 0 or source_sequence >= 4489:
@@ -75,11 +132,16 @@ def build_m49_tgs_full_shadow_router(*, root_provider: RootProvider = lambda: No
str(result.root), result_id, source_sequence, _evidence_signature(result.root) str(result.root), result_id, source_sequence, _evidence_signature(result.root)
) )
except (OSError, ValueError, KeyError, json.JSONDecodeError): except (OSError, ValueError, KeyError, json.JSONDecodeError):
raise HTTPException(status_code=503, detail="M49 TGS full-shadow spatial evidence failed verification") from None raise HTTPException(
status_code=503, detail="M49 TGS full-shadow spatial evidence failed verification"
) from None
return Response( return Response(
content=content, content=content,
media_type="application/json", media_type="application/json",
headers={"Cache-Control": "private, max-age=31536000, immutable", "X-Content-Type-Options": "nosniff"}, headers={
"Cache-Control": "private, max-age=31536000, immutable",
"X-Content-Type-Options": "nosniff",
},
) )
@router.get("/{result_id}/spatial/chunk") @router.get("/{result_id}/spatial/chunk")
@@ -134,7 +196,9 @@ def _frame_json_cached(
root_path = Path(root) root_path = Path(root)
frame_signature = (signature[-2], signature[-1]) frame_signature = (signature[-2], signature[-1])
frame = _frames(root, frame_signature)[source_sequence] frame = _frames(root, frame_signature)[source_sequence]
centers = np.load(root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False) centers = np.load(
root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False
)
states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False) states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False)
z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False) z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False)
states = states_all[source_sequence] states = states_all[source_sequence]
@@ -169,13 +233,20 @@ def _frame_json_cached(
], ],
}, },
"metrics": copy.deepcopy(frame), "metrics": copy.deepcopy(frame),
"state_codes": {"UNOBSERVED": 0, "GROUND_SUPPORT": 1, "NONGROUND_OCCUPIED": 2, "UNKNOWN_REJECTED": 3}, "state_codes": {
"UNOBSERVED": 0,
"GROUND_SUPPORT": 1,
"NONGROUND_OCCUPIED": 2,
"UNKNOWN_REJECTED": 3,
},
"aos_used": False, "aos_used": False,
"gpu_used": False, "gpu_used": False,
"authority": {"navigation_or_safety_accepted": False, "visual_quality_accepted": False}, "authority": {"navigation_or_safety_accepted": False, "visual_quality_accepted": False},
"access": "read-only", "access": "read-only",
} }
return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("utf-8") return json.dumps(
payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
).encode("utf-8")
@lru_cache(maxsize=16) @lru_cache(maxsize=16)
@@ -189,7 +260,9 @@ def _chunk_json_cached(
root_path = Path(root) root_path = Path(root)
frame_signature = (signature[-2], signature[-1]) frame_signature = (signature[-2], signature[-1])
frames = _frames(root, frame_signature) frames = _frames(root, frame_signature)
centers = np.load(root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False) centers = np.load(
root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False
)
states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False) states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False)
z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False) z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False)
if ( if (
@@ -258,6 +331,46 @@ def _chunk_json_cached(
).encode("utf-8") ).encode("utf-8")
def _artifact_descriptor(
result: M49TgsFullShadowResult,
name: str,
) -> dict[str, object]:
for artifact in result.manifest["artifacts"]:
if artifact.get("path") == name:
return artifact
raise ValueError(f"full-shadow artifact is not declared: {name}")
def _playback_manifest(result: M49TgsFullShadowResult) -> dict[str, object]:
tracks: list[dict[str, object]] = []
total_bytes = 0
for track_id, (name, media_type, dtype, shape) in PLAYBACK_TRACKS.items():
artifact = _artifact_descriptor(result, name)
byte_length = int(artifact["byte_length"])
total_bytes += byte_length
tracks.append(
{
"id": track_id,
"url": (f"{ENDPOINT_ROOT}/{result.result_id}/playback/tracks/{track_id}"),
"media_type": media_type,
"dtype": dtype,
"shape": shape,
"byte_length": byte_length,
"sha256": artifact["sha256"],
}
)
return {
"schema_version": "missioncore.m49-tgs-full-shadow-playback/v1",
"result_id": result.result_id,
"coordinate_frame": "map-gravity-local",
"frame_count": 4489,
"cell_count": 2244,
"total_byte_length": total_bytes,
"tracks": tracks,
"access": "read-only",
}
def _project(result: M49TgsFullShadowResult) -> dict[str, object]: def _project(result: M49TgsFullShadowResult) -> dict[str, object]:
return { return {
**copy.deepcopy(result.report), **copy.deepcopy(result.report),
@@ -269,7 +382,9 @@ def _project(result: M49TgsFullShadowResult) -> dict[str, object]:
} }
def _catalog(items: list[dict[str, object]], *, configured: bool, invalid_total: int) -> dict[str, object]: def _catalog(
items: list[dict[str, object]], *, configured: bool, invalid_total: int
) -> dict[str, object]:
return { return {
"schema_version": "missioncore.m49-tgs-full-shadow-catalog/v1", "schema_version": "missioncore.m49-tgs-full-shadow-catalog/v1",
"configured": configured, "configured": configured,
+83 -3
View File
@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import copy import copy
import hashlib
import json import json
import re import re
from collections.abc import Callable, Iterator from collections.abc import Callable, Iterator
@@ -11,6 +12,7 @@ from pathlib import Path
from typing import Final from typing import Final
from fastapi import APIRouter, HTTPException, Query, Response from fastapi import APIRouter, HTTPException, Query, Response
from fastapi.responses import StreamingResponse
from k1link.perception.threat_replay import ( from k1link.perception.threat_replay import (
THREAT_REPLAY_FRAME_SCHEMA, THREAT_REPLAY_FRAME_SCHEMA,
@@ -166,7 +168,7 @@ def build_m4_threat_replay_router(
def get_timeline(result_id: str) -> dict[str, object]: def get_timeline(result_id: str) -> dict[str, object]:
return copy.deepcopy(timeline(result_id).metadata()) return copy.deepcopy(timeline(result_id).metadata())
@router.get("/results/{result_id}/timeline/chunk") @router.get("/results/{result_id}/timeline/chunk", response_model=None)
def get_timeline_chunk( def get_timeline_chunk(
result_id: str, result_id: str,
start: int = Query(default=0, ge=0), start: int = Query(default=0, ge=0),
@@ -175,14 +177,87 @@ def build_m4_threat_replay_router(
ge=1, ge=1,
le=RECORDED_SPATIAL_MAX_CHUNK_FRAMES, le=RECORDED_SPATIAL_MAX_CHUNK_FRAMES,
), ),
) -> dict[str, object]: include_points: bool = Query(default=True),
) -> dict[str, object] | Response:
try: try:
return timeline(result_id).chunk(start_sequence=start, frame_count=count) payload = timeline(result_id).chunk(
start_sequence=start,
frame_count=count,
include_points=include_points,
)
except RecordedThreatTimelineError: except RecordedThreatTimelineError:
raise HTTPException( raise HTTPException(
status_code=404, status_code=404,
detail="M4.6 timeline chunk не найден", detail="M4.6 timeline chunk не найден",
) from None ) from None
if include_points:
return payload
return Response(
content=json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
allow_nan=False,
),
media_type="application/json",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"Content-Encoding": "identity",
"X-Content-Type-Options": "nosniff",
},
)
@router.get("/results/{result_id}/timeline/playback")
def get_timeline_playback(result_id: str) -> dict[str, object]:
projected = timeline(result_id)
points = projected.store.playback_points_map()
offsets = projected.store.playback_point_offsets()
content_sha256 = hashlib.sha256(memoryview(points).cast("B")).hexdigest()
return {
"schema_version": "missioncore.recorded-spatial-playback/v1",
"result_id": result_id,
"frame_count": len(offsets) - 1,
"point_count": int(points.shape[0]),
"point_offsets": list(offsets),
"track": {
"id": "points-map-f32",
"url": (
f"/api/v1/laboratory/m4-threat/results/{result_id}"
"/timeline/playback/tracks/points-map-f32"
),
"media_type": "application/octet-stream",
"dtype": "<f4",
"shape": [int(points.shape[0]), 3],
"bytes": int(points.nbytes),
"sha256": content_sha256,
},
"source_pack_sha256": projected.profile.source_pack_sha256,
"coordinate_frame": "map",
"ground_truth": False,
"authority": "replay-simulated",
"access": "read-only-sealed-binary-playback",
}
@router.get(
"/results/{result_id}/timeline/playback/tracks/points-map-f32",
response_class=StreamingResponse,
)
def get_timeline_playback_points(result_id: str) -> StreamingResponse:
projected = timeline(result_id)
points = projected.store.playback_points_map()
source_digest = projected.profile.source_pack_sha256
return StreamingResponse(
_binary_chunks(memoryview(points).cast("B")),
media_type="application/octet-stream",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"Content-Encoding": "identity",
"Content-Length": str(points.nbytes),
"ETag": f'"{source_digest}-points-map-f32"',
"X-Content-Type-Options": "nosniff",
"X-Uncompressed-Content-Length": str(points.nbytes),
},
)
@router.get("/results/{result_id}/timeline/frames/{sequence}/camera") @router.get("/results/{result_id}/timeline/frames/{sequence}/camera")
def get_timeline_camera(result_id: str, sequence: int) -> Response: def get_timeline_camera(result_id: str, sequence: int) -> Response:
@@ -196,6 +271,11 @@ def build_m4_threat_replay_router(
return router return router
def _binary_chunks(view: memoryview, chunk_size: int = 1024 * 1024) -> Iterator[bytes]:
for start in range(0, view.nbytes, chunk_size):
yield bytes(view[start : start + chunk_size])
@lru_cache(maxsize=4) @lru_cache(maxsize=4)
def _read_threat_result_cached( def _read_threat_result_cached(
root_value: str, root_value: str,
+31
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import importlib.util import importlib.util
import time import time
from pathlib import Path from pathlib import Path
from threading import Thread
from unittest.mock import patch from unittest.mock import patch
from k1link.perception.detector import DetectorFrameTiming from k1link.perception.detector import DetectorFrameTiming
@@ -157,3 +158,33 @@ def test_cyclic_gc_policy_collects_outside_hot_loop_and_restores_state() -> None
"pre_collected": 3, "pre_collected": 3,
"post_collected": 0, "post_collected": 0,
} }
def test_shared_start_barrier_publishes_readiness_and_waits_for_release(
tmp_path: Path,
) -> None:
ready = tmp_path / "graph.ready"
start = tmp_path / "start.signal"
completed: list[bool] = []
thread = Thread(
target=lambda: (
RUNNER.wait_for_shared_start(
ready_file=ready,
start_file=start,
timeout_seconds=1.0,
),
completed.append(True),
)
)
thread.start()
deadline = time.monotonic() + 1.0
while not ready.exists() and time.monotonic() < deadline:
time.sleep(0.005)
assert ready.read_text(encoding="utf-8") == "ready\n"
assert completed == []
start.write_text("start\n", encoding="utf-8")
thread.join(timeout=1.0)
assert completed == [True]
+63 -18
View File
@@ -8,7 +8,10 @@ from pathlib import Path
import numpy as np import numpy as np
from k1link.laboratory.m49_tgs_full_shadow import seal_m49_tgs_full_shadow from k1link.laboratory.m49_tgs_full_shadow import (
M49TgsFullShadowResult,
seal_m49_tgs_full_shadow,
)
from k1link.web import m49_tgs_full_shadow_api as full_shadow_api from k1link.web import m49_tgs_full_shadow_api as full_shadow_api
REPOSITORY_ROOT = Path(__file__).resolve().parents[1] REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
@@ -96,24 +99,34 @@ def test_full_shadow_seal_binds_visual_and_semantic_timelines(tmp_path: Path) ->
"files": files, "files": files,
} }
(source / "result.json").write_text(json.dumps(worker), encoding="utf-8") (source / "result.json").write_text(json.dumps(worker), encoding="utf-8")
(source / "worker-summary.json").write_text(json.dumps({ (source / "worker-summary.json").write_text(
"schema_version": "missioncore.m49-tgs-full-shadow-worker-summary/v1", json.dumps(
"gpu_requested": False, {
"aos_used": False, "schema_version": "missioncore.m49-tgs-full-shadow-worker-summary/v1",
"all_timeline_frames_accounted": True, "gpu_requested": False,
"all_eligible_points_accounted": True, "aos_used": False,
"canonical_triton_health": "healthy", "all_timeline_frames_accounted": True,
"canonical_triton_id": "triton", "all_eligible_points_accounted": True,
"wall_seconds": 1.0, "canonical_triton_health": "healthy",
}), encoding="utf-8") "canonical_triton_id": "triton",
"wall_seconds": 1.0,
}
),
encoding="utf-8",
)
profile = tmp_path / "profile.json" profile = tmp_path / "profile.json"
profile.write_text(json.dumps({ profile.write_text(
"schema_version": "missioncore.m49-tgs-full-shadow-profile/v1", json.dumps(
"profile_id": "test", {
"profile": {"history_seconds": 1.0}, "schema_version": "missioncore.m49-tgs-full-shadow-profile/v1",
"costmap": {"state_priority": ["NONGROUND_OCCUPIED"]}, "profile_id": "test",
"state_codes": {"UNOBSERVED": 0}, "profile": {"history_seconds": 1.0},
}), encoding="utf-8") "costmap": {"state_priority": ["NONGROUND_OCCUPIED"]},
"state_codes": {"UNOBSERVED": 0},
}
),
encoding="utf-8",
)
visual = "m4-threat-replay-" + "d" * 64 visual = "m4-threat-replay-" + "d" * 64
semantic = "e47-semantic-slam-" + "e" * 64 semantic = "e47-semantic-slam-" + "e" * 64
@@ -176,3 +189,35 @@ def test_full_shadow_chunk_contract_keeps_missing_lidar_unobserved(
assert len(payload["costmap"]["centers_xy_m"]) == 2244 assert len(payload["costmap"]["centers_xy_m"]) == 2244
assert payload["frames"][1]["sample_available"] is False assert payload["frames"][1]["sample_available"] is False
assert set(payload["frames"][1]["states"]) == {0} assert set(payload["frames"][1]["states"]) == {0}
def test_full_shadow_playback_manifest_exposes_sealed_binary_tracks(tmp_path: Path) -> None:
names = {descriptor[0] for descriptor in full_shadow_api.PLAYBACK_TRACKS.values()}
artifacts = [
{
"path": name,
"byte_length": index + 10,
"sha256": f"{index + 1:064x}",
}
for index, name in enumerate(sorted(names))
]
result = M49TgsFullShadowResult(
"m49-tgs-full-shadow-" + "a" * 64,
tmp_path,
{"artifacts": artifacts},
{},
)
payload = full_shadow_api._playback_manifest(result)
assert payload["schema_version"] == "missioncore.m49-tgs-full-shadow-playback/v1"
assert payload["frame_count"] == 4489
assert payload["cell_count"] == 2244
assert payload["total_byte_length"] == sum(item["byte_length"] for item in artifacts)
assert {item["id"] for item in payload["tracks"]} == {
"frames",
"centers",
"states",
"z-bounds",
}
assert all("/playback/tracks/" in item["url"] for item in payload["tracks"])
@@ -0,0 +1,209 @@
from __future__ import annotations
import importlib.util
import json
import tarfile
from pathlib import Path
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
EVIDENCE_PATH = (
REPOSITORY_ROOT
/ "experiments/perception/worker/m49_t3_travel/build_tgs_integrated_graph_evidence.py"
)
ARTIFACT_PATH = REPOSITORY_ROOT / "scripts/build_m49_tgs_integrated_graph_worker_artifact.py"
def load_module(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
EVIDENCE = load_module("m49_tgs_integrated_evidence", EVIDENCE_PATH)
ARTIFACT = load_module("m49_tgs_integrated_artifact", ARTIFACT_PATH)
def test_integrated_gate_joins_all_frames_and_preserves_false_authority(tmp_path: Path) -> None:
profile_path = tmp_path / "profile.json"
profile_path.write_text(
json.dumps(
{
"schema_version": EVIDENCE.PROFILE_SCHEMA,
"profile_id": "test",
"source": {
"source_id": "RAVNOVES00",
"source_pack_sha256": "a" * 64,
"requested_source_rate_hz": 12.0,
},
"stages": {
"reference_graph": {
"graph_config_sha256": "b" * 64,
"detector_profile_sha256": "z" * 64,
},
"tgs": {
"profile_sha256": "c" * 64,
"linked_accepted_result_id": "m49-tgs-full-shadow-" + "d" * 64,
},
},
"acceptance": {
"minimum_delivery_ratio": 1.0,
"reference_world_state_fps": 11.79902,
"maximum_world_state_fps_regression_fraction": 0.05,
"minimum_effective_world_state_fps": 11.209069,
"maximum_world_state_completion_p95_ms": 125.0,
"candidate_stage_p95_ms_max": 25.0,
"candidate_stage_p99_ms_max": 50.0,
"combined_output_age_p99_ms_max": 125.0,
"capacity_drop_count_max": 0,
},
"authority": {
"visual_quality_accepted": False,
"traversability_accepted": False,
"physical_free_space_accepted": False,
"commands_enabled": False,
"actuation_allowed": False,
"navigation_or_safety_accepted": False,
"production_accepted": False,
},
}
),
encoding="utf-8",
)
graph_result = tmp_path / "graph-result.json"
graph_result.write_text(
json.dumps(
{
"schema_version": EVIDENCE.GRAPH_SCHEMA,
"execution": {
"admitted_frames": EVIDENCE.FRAME_COUNT,
"delivered_world_states": EVIDENCE.FRAME_COUNT,
"effective_world_state_fps": 11.75,
"delivery_ratio": 1.0,
"terminal_outcomes": {"delivered": EVIDENCE.FRAME_COUNT},
"requested_source_rate_hz": 12.0,
},
"identity": {
"inputs": {
"graph_config": "b" * 64,
"detector_profile": "z" * 64,
}
},
"metrics": {
"world_state_completion_age_ms": {"p95": 40.0},
"gpu": {"sample_count": 2},
},
"evidence_integrity_gate_passed": True,
}
),
encoding="utf-8",
)
graph_frames = tmp_path / "graph-frames.jsonl"
graph_frames.write_text(
"".join(
json.dumps(
{
"source_envelope": {"sequence": index},
"completion_age_ns": 40_000_000,
}
)
+ "\n"
for index in range(EVIDENCE.FRAME_COUNT)
),
encoding="utf-8",
)
tgs_result = tmp_path / "tgs-result.json"
tgs_result.write_text(
json.dumps(
{
"schema_version": EVIDENCE.TGS_SCHEMA,
"status": "passed",
"config_sha256": "c" * 64,
"timeline": {
"frame_count": EVIDENCE.FRAME_COUNT,
"available_lidar_frame_count": 3928,
},
"performance": {
"candidate_tgs_ms": {"p95": 2.0, "p99": 3.0},
"completion_age_ms": {"p99": 5.0},
"capacity_drop_count": 0,
},
}
),
encoding="utf-8",
)
tgs_timing = tmp_path / "tgs-timing.tsv"
tgs_timing.write_text(
"timeline_frame_index\tcompletion_age_ms\n"
+ "".join(f"{index}\t5.0\n" for index in range(EVIDENCE.FRAME_COUNT)),
encoding="utf-8",
)
telemetry = tmp_path / "telemetry.jsonl"
telemetry.write_text(
"".join(
json.dumps(
{
"role": role,
"cpu_percent": "10.0%",
"memory_usage": "1GiB / 64GiB",
"memory_percent": "1.56%",
}
)
+ "\n"
for role in ("graph", "tgs", "triton")
),
encoding="utf-8",
)
output = tmp_path / "result.json"
result = EVIDENCE.build(
profile_path=profile_path,
graph_result_path=graph_result,
graph_frames_path=graph_frames,
tgs_result_path=tgs_result,
tgs_timing_path=tgs_timing,
telemetry_path=telemetry,
output_path=output,
release_sha256="e" * 64,
)
assert result["status"] == "passed"
assert result["source"]["joined_frame_count"] == EVIDENCE.FRAME_COUNT
assert result["performance"]["combined_output_age_ms"]["p99"] == 40.0
assert result["checks"]["authority_remains_false"] is True
assert result["production_accepted"] is False
def test_worker_artifact_is_deterministic_and_excludes_gauss(monkeypatch, tmp_path: Path) -> None:
def fake_wheel(_source_root: Path, output: Path) -> Path:
output.mkdir(parents=True, exist_ok=True)
wheel = output / ARTIFACT.WHEEL_NAME
wheel.write_bytes(b"clean committed wheel\n")
return wheel
monkeypatch.setattr(ARTIFACT, "build_wheel", fake_wheel)
revision = "f" * 40
first = ARTIFACT.build_artifact(
"mission-core-m49-integrated-unit-001",
tmp_path / "first",
revision=revision,
source_root=REPOSITORY_ROOT,
)
second = ARTIFACT.build_artifact(
"mission-core-m49-integrated-unit-001",
tmp_path / "second",
revision=revision,
source_root=REPOSITORY_ROOT,
)
assert Path(first["artifact"]).read_bytes() == Path(second["artifact"]).read_bytes()
with tarfile.open(first["artifact"], "r:gz") as archive:
names = set(archive.getnames())
release_stream = archive.extractfile("payload/release.json")
assert release_stream is not None
release = json.loads(release_stream.read())
assert not any("gauss" in name.lower() or "playcanvas" in name.lower() for name in names)
assert "payload/build_tgs_fail_closed_evidence.py" in names
assert release["scope"]["gauss_or_playcanvas_action"] == "none"
assert all(value is False for value in release["authority"].values())
+19
View File
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import json
from pathlib import Path from pathlib import Path
from fastapi.routing import APIRoute from fastapi.routing import APIRoute
@@ -126,6 +127,7 @@ def test_m4_6_lab_api_projects_report_and_exact_visual_frame() -> None:
def test_m4_6_timeline_is_indexed_and_spatial_evidence_is_chunked() -> None: def test_m4_6_timeline_is_indexed_and_spatial_evidence_is_chunked() -> None:
get_timeline = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/timeline") get_timeline = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/timeline")
get_chunk = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/timeline/chunk") get_chunk = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/timeline/chunk")
get_playback = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/timeline/playback")
timeline = get_timeline(RESULT_ID) timeline = get_timeline(RESULT_ID)
assert timeline["schema_version"] == "missioncore.recorded-spatial-evidence-timeline/v1" assert timeline["schema_version"] == "missioncore.recorded-spatial-evidence-timeline/v1"
@@ -160,6 +162,23 @@ def test_m4_6_timeline_is_indexed_and_spatial_evidence_is_chunked() -> None:
assert 0 < first["point_cloud_sample_count"] <= 4096 assert 0 < first["point_cloud_sample_count"] <= 4096
assert first["camera_url"].endswith(f"/{RESULT_ID}/timeline/frames/1880/camera") assert first["camera_url"].endswith(f"/{RESULT_ID}/timeline/frames/1880/camera")
lightweight_response = get_chunk(RESULT_ID, start=1880, count=1, include_points=False)
lightweight = json.loads(lightweight_response.body)
assert lightweight["frames"][0]["point_cloud_body_xyz_m"] == []
assert lightweight["frames"][0]["point_cloud_source_count"] == first["point_cloud_source_count"]
assert lightweight["frames"][0]["point_cloud_sample_count"] == first["point_cloud_sample_count"]
playback = get_playback(RESULT_ID)
assert playback["schema_version"] == "missioncore.recorded-spatial-playback/v1"
assert playback["frame_count"] == 4489
assert playback["point_count"] == 9_207_270
assert len(playback["point_offsets"]) == 4490
assert playback["point_offsets"][-1] == playback["point_count"]
assert playback["track"]["dtype"] == "<f4"
assert playback["track"]["shape"] == [9_207_270, 3]
assert playback["track"]["bytes"] == 110_487_240
assert len(playback["track"]["sha256"]) == 64
def test_m4_6_exact_camera_endpoint_is_bound_to_selected_visual_sequence() -> None: def test_m4_6_exact_camera_endpoint_is_bound_to_selected_visual_sequence() -> None:
calls: list[tuple[str, int]] = [] calls: list[tuple[str, int]] = []