fix(lab): preserve replay view and restore SLAM semantics

This commit is contained in:
DCCONSTRUCTIONS
2026-08-26 23:37:05 +03:00
parent 46cef8df17
commit 296cf610cd
8 changed files with 230 additions and 45 deletions
@@ -1,6 +1,7 @@
import type { LaboratoryFetch } from "./advancedResults"; import type { LaboratoryFetch } from "./advancedResults";
const RESULT_ID = /^m49-tgs-full-shadow-[a-f0-9]{64}$/; const RESULT_ID = /^m49-tgs-full-shadow-[a-f0-9]{64}$/;
const SEMANTIC_RESULT_ID = /^e47-semantic-slam-[a-f0-9]{64}$/;
export type M49TgsFullShadowStateCode = 0 | 1 | 2 | 3; export type M49TgsFullShadowStateCode = 0 | 1 | 2 | 3;
@@ -10,6 +11,7 @@ export interface M49TgsFullShadowResult {
source: { source: {
sourcePackSha256: string; sourcePackSha256: string;
linkedVisualResultId: string; linkedVisualResultId: string;
linkedSemanticResultId: string;
}; };
configuration: { configuration: {
configSha256: string; configSha256: string;
@@ -158,12 +160,20 @@ export async function fetchM49TgsFullShadowResult(
const performance = objectValue(payload.performance, "M49 full performance"); const performance = objectValue(payload.performance, "M49 full performance");
const acceptance = objectValue(payload.acceptance, "M49 full acceptance"); const acceptance = objectValue(payload.acceptance, "M49 full acceptance");
const decision = objectValue(payload.decision, "M49 full decision"); const decision = objectValue(payload.decision, "M49 full decision");
const linkedSemanticResultId = text(
source.linked_semantic_result_id,
"M49 full semantic result",
);
if (!SEMANTIC_RESULT_ID.test(linkedSemanticResultId)) {
throw new M49TgsFullShadowContractError("M49 full semantic result: нарушена идентичность.");
}
return { return {
resultId: text(payload.result_id, "M49 full result"), resultId: text(payload.result_id, "M49 full result"),
createdAtUtc: text(payload.created_at_utc, "M49 full created"), createdAtUtc: text(payload.created_at_utc, "M49 full created"),
source: { source: {
sourcePackSha256: text(source.source_pack_sha256, "M49 full source pack"), sourcePackSha256: text(source.source_pack_sha256, "M49 full source pack"),
linkedVisualResultId: text(source.linked_visual_result_id, "M49 full visual result"), linkedVisualResultId: text(source.linked_visual_result_id, "M49 full visual result"),
linkedSemanticResultId,
}, },
configuration: { configuration: {
configSha256: text(configuration.config_sha256, "M49 full config"), configSha256: text(configuration.config_sha256, "M49 full config"),
@@ -4,6 +4,10 @@ import type {
RecordedEvidenceSemanticClass, RecordedEvidenceSemanticClass,
RecordedEvidenceSemanticPaletteEntry, RecordedEvidenceSemanticPaletteEntry,
} from "../../components/laboratory/RecordedEvidenceSemanticMaskOverlay"; } from "../../components/laboratory/RecordedEvidenceSemanticMaskOverlay";
import {
fetchE47SemanticSlamResult,
type E47SemanticSlamResult,
} from "../../core/laboratory/e47SemanticSlam";
import { import {
fetchM49TgsFullShadowSpatialChunk, fetchM49TgsFullShadowSpatialChunk,
type M49TgsFullShadowResult, type M49TgsFullShadowResult,
@@ -46,6 +50,8 @@ function message(error: unknown): string {
export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowResult }) { export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowResult }) {
const [activeSequence, setActiveSequence] = useState<number | null>(null); const [activeSequence, setActiveSequence] = useState<number | null>(null);
const [semantic, setSemantic] = useState<E47SemanticSlamResult | null>(null);
const [semanticError, setSemanticError] = useState<string | null>(null);
const [chunks, setChunks] = useState<ReadonlyMap<number, M49TgsFullShadowSpatialChunk>>( const [chunks, setChunks] = useState<ReadonlyMap<number, M49TgsFullShadowSpatialChunk>>(
() => new Map(), () => new Map(),
); );
@@ -57,6 +63,27 @@ export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowR
const activeChunkStartRef = useRef(activeChunkStart); const activeChunkStartRef = useRef(activeChunkStart);
activeChunkStartRef.current = activeChunkStart; activeChunkStartRef.current = activeChunkStart;
useEffect(() => {
const controller = new AbortController();
setSemantic(null);
setSemanticError(null);
void fetchE47SemanticSlamResult({
resultId: result.source.linkedSemanticResultId,
signal: controller.signal,
})
.then((next) => {
if (controller.signal.aborted) return;
if (!next || next.baseM4ResultId !== result.source.linkedVisualResultId) {
throw new Error("Semantic archive не совпал с исходным M4 timeline.");
}
setSemantic(next);
})
.catch((caught: unknown) => {
if (!controller.signal.aborted) setSemanticError(message(caught));
});
return () => controller.abort();
}, [result.source.linkedSemanticResultId, result.source.linkedVisualResultId]);
useEffect(() => { useEffect(() => {
for (const controller of inFlightRef.current.values()) controller.abort(); for (const controller of inFlightRef.current.values()) controller.abort();
inFlightRef.current.clear(); inFlightRef.current.clear();
@@ -142,23 +169,34 @@ export function M49TgsFullShadowEvidence({ result }: { result: M49TgsFullShadowR
}, []); }, []);
return ( return (
<M4ReplayThreatVisual <>
resultId={result.source.linkedVisualResultId} <M4ReplayThreatVisual
showReviewAnchorBoxes={false} resultId={result.source.linkedVisualResultId}
reviewLabel="4 489 source-paced TGS frames" semantic={semantic ? {
evidenceLabel="M49 · full TGS shadow" resultId: semantic.resultId,
initialSpatialMode="3d" taxonomy: semantic.taxonomy,
onActiveSequenceChange={handleSequenceChange} } : undefined}
classifiedSpatialLayer={{ showReviewAnchorBoxes={false}
label: "TGS full shadow · causal rolling 1 s", reviewLabel="4 489 source-paced TGS frames"
pointLayerLabel: "SOURCE POINTS", evidenceLabel="M49 · full TGS shadow"
cellLayerLabel: "TGS COSTMAP", initialSpatialMode="3d"
expectedAtSequence: true, onActiveSequenceChange={handleSequenceChange}
frame: classifiedFrame, classifiedSpatialLayer={{
loading, label: "TGS full shadow · causal rolling 1 s",
error, pointLayerLabel: "SOURCE POINTS",
replacePointCloud: false, cellLayerLabel: "TGS COSTMAP",
}} expectedAtSequence: true,
/> frame: classifiedFrame,
loading,
error,
replacePointCloud: false,
}}
/>
{semanticError ? (
<div className="m4-replay-threat-visual__pane-status" role="alert">
Semantic overlay недоступен: {semanticError}
</div>
) : null}
</>
); );
} }
@@ -409,6 +409,17 @@ export function M4ReplayThreatVisual({
const classifiedSpatialFrame = classifiedSpatialLayer?.frame?.sourceSequence === timelineFrame.activeSequence const classifiedSpatialFrame = classifiedSpatialLayer?.frame?.sourceSequence === timelineFrame.activeSequence
? classifiedSpatialLayer?.frame ?? null ? classifiedSpatialLayer?.frame ?? null
: null; : null;
const lastClassifiedSpatialFrameRef = useRef<{
resultId: string;
frame: M4ReplayClassifiedSpatialFrame;
} | null>(null);
if (classifiedSpatialLayer?.frame) {
lastClassifiedSpatialFrameRef.current = { resultId, frame: classifiedSpatialLayer.frame };
}
const displayedClassifiedSpatialFrame = classifiedSpatialFrame
?? (lastClassifiedSpatialFrameRef.current?.resultId === resultId
? lastClassifiedSpatialFrameRef.current.frame
: null);
const replaceClassifiedPointCloud = classifiedSpatialLayer?.replacePointCloud ?? true; const replaceClassifiedPointCloud = classifiedSpatialLayer?.replacePointCloud ?? true;
const nominalSensorHeightM = metadata.timeline?.rig.nominalSensorHeightM ?? 0; const nominalSensorHeightM = metadata.timeline?.rig.nominalSensorHeightM ?? 0;
const mapGravityLocalSensorToBodyGround = useCallback(( const mapGravityLocalSensorToBodyGround = useCallback((
@@ -425,13 +436,13 @@ export function M4ReplayThreatVisual({
return [rotated[0], rotated[1], rotated[2] + nominalSensorHeightM]; return [rotated[0], rotated[1], rotated[2] + nominalSensorHeightM];
}, [activeSpatialFrame?.bodyFrame?.basisMapFromBody, nominalSensorHeightM]); }, [activeSpatialFrame?.bodyFrame?.basisMapFromBody, nominalSensorHeightM]);
const classifiedPointsBody = useMemo( const classifiedPointsBody = useMemo(
() => classifiedSpatialFrame?.pointsMapGravityLocalXyzM.map( () => displayedClassifiedSpatialFrame?.pointsMapGravityLocalXyzM.map(
mapGravityLocalSensorToBodyGround, mapGravityLocalSensorToBodyGround,
) ?? [], ) ?? [],
[classifiedSpatialFrame, mapGravityLocalSensorToBodyGround], [displayedClassifiedSpatialFrame, mapGravityLocalSensorToBodyGround],
); );
const classifiedCellsBody = useMemo<readonly LaboratoryMetricCellEvidence[]>( const classifiedCellsBody = useMemo<readonly LaboratoryMetricCellEvidence[]>(
() => classifiedSpatialFrame?.cellsMapGravityLocal.map((cell) => { () => displayedClassifiedSpatialFrame?.cellsMapGravityLocal.map((cell) => {
const body = mapGravityLocalSensorToBodyGround([ const body = mapGravityLocalSensorToBodyGround([
cell.centerXyM[0], cell.centerXyM[0],
cell.centerXyM[1], cell.centerXyM[1],
@@ -451,7 +462,7 @@ export function M4ReplayThreatVisual({
state: cell.state, state: cell.state,
}; };
}) ?? [], }) ?? [],
[classifiedSpatialFrame, mapGravityLocalSensorToBodyGround, nominalSensorHeightM], [displayedClassifiedSpatialFrame, mapGravityLocalSensorToBodyGround, nominalSensorHeightM],
); );
const classifiedCellCounts = useMemo(() => ({ const classifiedCellCounts = useMemo(() => ({
ground: classifiedSpatialFrame?.cellsMapGravityLocal.filter( ground: classifiedSpatialFrame?.cellsMapGravityLocal.filter(
@@ -641,6 +652,16 @@ export function M4ReplayThreatVisual({
> >
{classifiedSpatialLayer.pointLayerLabel} {classifiedSpatialLayer.pointLayerLabel}
</Button> </Button>
<Button
size="compact"
shape="pill"
variant={showLocalSurface ? "primary" : "secondary"}
aria-pressed={showLocalSurface}
title="Bounded local SLAM surface · visual-derived"
onClick={() => setShowLocalSurface((visible) => !visible)}
>
LOCAL SLAM
</Button>
<Button <Button
size="compact" size="compact"
shape="pill" shape="pill"
@@ -650,6 +671,17 @@ export function M4ReplayThreatVisual({
> >
{classifiedSpatialLayer.cellLayerLabel} {classifiedSpatialLayer.cellLayerLabel}
</Button> </Button>
{semantic ? (
<Button
size="compact"
shape="pill"
variant={showSpatialSemantic ? "primary" : "secondary"}
aria-pressed={showSpatialSemantic}
onClick={() => setShowSpatialSemantic((visible) => !visible)}
>
SEMANTICS
</Button>
) : null}
</div> </div>
) : ( ) : (
<div <div
@@ -952,38 +984,38 @@ export function M4ReplayThreatVisual({
</div> </div>
</div> </div>
) : null} ) : null}
{(!classifiedSpatialLayer ? spatialFrame : classifiedSpatialFrame) ? ( {(!classifiedSpatialLayer ? spatialFrame : displayedClassifiedSpatialFrame) ? (
<LaboratoryMetricEvidenceScene <LaboratoryMetricEvidenceScene
ref={metricSceneRef} ref={metricSceneRef}
pointCloudBodyXyzM={classifiedSpatialFrame && replaceClassifiedPointCloud pointCloudBodyXyzM={displayedClassifiedSpatialFrame && replaceClassifiedPointCloud
? classifiedPointsBody ? classifiedPointsBody
: activeSpatialFrame?.pointCloudBodyXyzM ?? []} : activeSpatialFrame?.pointCloudBodyXyzM ?? []}
localSurfaceBodyXyzM={classifiedSpatialFrame ? [] : localSurface.pointsBodyXyzM} localSurfaceBodyXyzM={localSurface.pointsBodyXyzM}
obstacles={classifiedSpatialFrame ? [] : sceneObstacles} obstacles={displayedClassifiedSpatialFrame ? [] : sceneObstacles}
rig={timeline.rig} rig={timeline.rig}
corridor={timeline.corridor} corridor={timeline.corridor}
occupiedVoxelSizeM={classifiedSpatialFrame?.cellSizeM ?? timeline.occupiedVoxelSizeM} occupiedVoxelSizeM={displayedClassifiedSpatialFrame?.cellSizeM ?? timeline.occupiedVoxelSizeM}
mode={spatialMode} mode={spatialMode}
label={`${evidenceLabel} exact current increment, bounded local SLAM surface and rolling occupancy`} label={`${evidenceLabel} exact current increment, bounded local SLAM surface and rolling occupancy`}
showCurrentIncrement={showCurrentIncrement} showCurrentIncrement={showCurrentIncrement}
showLocalSurface={classifiedSpatialFrame ? false : showLocalSurface} showLocalSurface={showLocalSurface}
showRollingMap={showRollingMap} showRollingMap={showRollingMap}
showLowStep={classifiedSpatialFrame ? false : showLowStep} showLowStep={displayedClassifiedSpatialFrame ? false : showLowStep}
pointSemanticClassIds={classifiedSpatialFrame && replaceClassifiedPointCloud pointSemanticClassIds={displayedClassifiedSpatialFrame && replaceClassifiedPointCloud
? classifiedSpatialFrame.pointClassIds ? displayedClassifiedSpatialFrame.pointClassIds
: alignedSemanticPointIds} : alignedSemanticPointIds}
semanticClasses={classifiedSpatialFrame && replaceClassifiedPointCloud semanticClasses={displayedClassifiedSpatialFrame && replaceClassifiedPointCloud
? classifiedSpatialFrame.classes ? displayedClassifiedSpatialFrame.classes
: semanticClasses} : semanticClasses}
semanticPalette={classifiedSpatialFrame && replaceClassifiedPointCloud semanticPalette={displayedClassifiedSpatialFrame && replaceClassifiedPointCloud
? classifiedSpatialFrame.palette ? displayedClassifiedSpatialFrame.palette
: semanticPalette} : semanticPalette}
classifiedCells={classifiedCellsBody} classifiedCells={classifiedCellsBody}
classifiedCellSizeM={classifiedSpatialFrame?.cellSizeM} classifiedCellSizeM={displayedClassifiedSpatialFrame?.cellSizeM}
showClassifiedCells={showRollingMap} showClassifiedCells={showRollingMap}
/> />
) : null} ) : null}
{classifiedSpatialLayer && !classifiedSpatialFrame ? ( {classifiedSpatialLayer && !displayedClassifiedSpatialFrame ? (
<div className="l3-visual-audit__state" role={classifiedSpatialLayer.error ? "alert" : "status"}> <div className="l3-visual-audit__state" role={classifiedSpatialLayer.error ? "alert" : "status"}>
{classifiedSpatialLayer.loading || displayingBufferedFrame {classifiedSpatialLayer.loading || displayingBufferedFrame
? <span className="busy-indicator" aria-hidden="true" /> ? <span className="busy-indicator" aria-hidden="true" />
@@ -74,12 +74,22 @@ test("M4.9T5 chunk keeps every missing-LiDAR cell explicitly UNOBSERVED", async
}); });
test("M4.9T5 viewer prefetches 24-frame immutable chunks", async () => { test("M4.9T5 viewer prefetches 24-frame immutable chunks", async () => {
const source = await readFile( const [source, contract] = await Promise.all([
new URL("../src/workspaces/laboratory/M49TgsFullShadowEvidence.tsx", import.meta.url), readFile(
"utf8", new URL("../src/workspaces/laboratory/M49TgsFullShadowEvidence.tsx", import.meta.url),
); "utf8",
),
readFile(
new URL("../src/core/laboratory/m49TgsFullShadow.ts", import.meta.url),
"utf8",
),
]);
assert.match(source, /const CHUNK_FRAMES = 24/); assert.match(source, /const CHUNK_FRAMES = 24/);
assert.match(source, /activeChunkStart \+ CHUNK_FRAMES/); assert.match(source, /activeChunkStart \+ CHUNK_FRAMES/);
assert.match(source, /fetchM49TgsFullShadowSpatialChunk/); assert.match(source, /fetchM49TgsFullShadowSpatialChunk/);
assert.match(source, /sampleAvailable: spatial\.sampleAvailable/); assert.match(source, /sampleAvailable: spatial\.sampleAvailable/);
assert.match(source, /fetchE47SemanticSlamResult/);
assert.match(source, /next\.baseM4ResultId !== result\.source\.linkedVisualResultId/);
assert.match(source, /semantic=\{semantic \? \{/);
assert.match(contract, /linked_semantic_result_id/);
}); });
@@ -801,12 +801,15 @@ test("M4.6 viewer keeps media and spatial panes on one playback clock", async ()
assert.match(visual, /showLocalSurface/); assert.match(visual, /showLocalSurface/);
assert.match( assert.match(
visual, visual,
/pointCloudBodyXyzM=\{classifiedSpatialFrame && replaceClassifiedPointCloud[\s\S]*\? classifiedPointsBody[\s\S]*: activeSpatialFrame\?\.pointCloudBodyXyzM \?\? \[\]\}/, /pointCloudBodyXyzM=\{displayedClassifiedSpatialFrame && replaceClassifiedPointCloud[\s\S]*\? classifiedPointsBody[\s\S]*: activeSpatialFrame\?\.pointCloudBodyXyzM \?\? \[\]\}/,
); );
assert.match( assert.match(
visual, visual,
/const activeSpatialFrame = spatialFrame\?\.sequence === timelineFrame\.activeSequence[\s\S]*const classifiedSpatialFrame = classifiedSpatialLayer\?\.frame\?\.sourceSequence === timelineFrame\.activeSequence/, /const classifiedSpatialFrame = classifiedSpatialLayer\?\.frame\?\.sourceSequence === timelineFrame\.activeSequence[\s\S]*const displayedClassifiedSpatialFrame = classifiedSpatialFrame[\s\S]*lastClassifiedSpatialFrameRef/,
); );
assert.match(visual, /localSurfaceBodyXyzM=\{localSurface\.pointsBodyXyzM\}/);
assert.match(visual, /showLocalSurface=\{showLocalSurface\}/);
assert.match(visual, /\{semantic \? \([\s\S]*>\s*SEMANTICS\s*<\/Button>/);
assert.match(visual, /все 2 244 TGS-ячейки явно UNOBSERVED/); assert.match(visual, /все 2 244 TGS-ячейки явно UNOBSERVED/);
assert.match( assert.match(
visual, visual,
@@ -20,13 +20,14 @@ correct for vegetation, terrain, gaps or the future vehicle envelope.
| Item | Identity | | Item | Identity |
| --- | --- | | --- | --- |
| LAB result | `m49-tgs-full-shadow-0faeaaf3aba8dccae974eab51ff9cccf264a13ec285abb1920c1e5a09a7e87bc` | | LAB result | `m49-tgs-full-shadow-ef98de7db7596d48e8c8c0549ce68e6704ee03e87c8c4bcf1e3e748b7ccb032e` |
| Worker run | `Worker 006 / ravnoves00-full-001` | | Worker run | `Worker 006 / ravnoves00-full-001` |
| Mission Core revision used by Worker | `40c850b167dda366d8aa45d828520168affaf9fd` | | Mission Core revision used by Worker | `40c850b167dda366d8aa45d828520168affaf9fd` |
| Deterministic Worker artifact | `5e0ea16c7a5cc760463836718b0cd8b0006ffc4b202e5f706a20a86ef2f912ab` | | Deterministic Worker artifact | `5e0ea16c7a5cc760463836718b0cd8b0006ffc4b202e5f706a20a86ef2f912ab` |
| Source pack SHA-256 | `0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944` | | Source pack SHA-256 | `0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944` |
| TGS config SHA-256 | `c2e07010aaee78259d36c057962d6bfb885349251ff7356d867e5813e632881c` | | TGS config SHA-256 | `c2e07010aaee78259d36c057962d6bfb885349251ff7356d867e5813e632881c` |
| Linked visual result | `m4-threat-replay-2a953c5f27f2a5b1dddc5c658c1de2c323d7796084a099c024987a1da03aa324` | | Linked visual result | `m4-threat-replay-2a953c5f27f2a5b1dddc5c658c1de2c323d7796084a099c024987a1da03aa324` |
| Linked semantic result | `e47-semantic-slam-f8c53ba12e9719856195890c7dcd2ba6c114434674159f948064368012e6b3bc` |
The profile is a `0.45 m`, `12 m` radius, `1 s` causal rolling The profile is a `0.45 m`, `12 m` radius, `1 s` causal rolling
`map-gravity-local` costmap. The four states remain separate: `map-gravity-local` costmap. The four states remain separate:
@@ -62,7 +63,9 @@ source points. This preserves chronology without inventing free space.
The immutable result is published on the canonical Mission Core service at The immutable result is published on the canonical Mission Core service at
port `8000`. The LAB reuses the recorded camera timeline and exposes independent port `8000`. The LAB reuses the recorded camera timeline and exposes independent
`SOURCE POINTS`, `TGS COSTMAP`, `3D` and `PLAN` controls. `SOURCE POINTS`, `LOCAL SLAM`, `TGS COSTMAP`, `SEMANTICS`, `3D` and `PLAN`
controls. The semantic mask is the exact E47 full-route archive bound to the
same M4 camera result; it remains diagnostic and does not change TGS states.
The first implementation fetched one large JSON frame at a time. Browser QA The first implementation fetched one large JSON frame at a time. Browser QA
showed that this was only intermittently exact at `1×`. The published viewer showed that this was only intermittently exact at `1×`. The published viewer
@@ -73,6 +76,14 @@ showed the loading placeholder. A real missing-LiDAR frame was separately
accepted with `0 source points`, `2,244 unobserved`, `0 occupied` and no retained accepted with `0 source points`, `2,244 unobserved`, `0 occupied` and no retained
previous cloud. previous cloud.
A follow-up playback acceptance fixed a UI lifecycle defect that remounted the
Three.js scene while the next TGS frame crossed the React buffer boundary. The
scene now retains the last sealed classified frame until the exact next frame
arrives, so orbit controls and the operator-selected view survive continuous
playback. Twelve consecutive `250 ms` playback samples kept one mounted 3D
canvas, a visible bounded `LOCAL SLAM` surface and the synchronized E47 video
mask. Pausing is no longer required to rotate, pan or zoom the 3D view.
## Sealed evidence ## Sealed evidence
| File | Bytes | SHA-256 | | File | Bytes | SHA-256 |
@@ -79,6 +79,7 @@ def seal_m49_tgs_full_shadow(
destination_root: Path, destination_root: Path,
profile_path: Path, profile_path: Path,
linked_visual_result_id: str, linked_visual_result_id: str,
linked_semantic_result_id: str,
created_at_utc: str | None = None, created_at_utc: str | None = None,
) -> M49TgsFullShadowResult: ) -> M49TgsFullShadowResult:
source = source_root.expanduser().resolve(strict=True) source = source_root.expanduser().resolve(strict=True)
@@ -115,6 +116,11 @@ def seal_m49_tgs_full_shadow(
or len(linked_visual_result_id) != len("m4-threat-replay-") + 64 or len(linked_visual_result_id) != len("m4-threat-replay-") + 64
): ):
raise M49TgsFullShadowError("linked visual result is invalid") raise M49TgsFullShadowError("linked visual result is invalid")
if (
not linked_semantic_result_id.startswith("e47-semantic-slam-")
or len(linked_semantic_result_id) != len("e47-semantic-slam-") + 64
):
raise M49TgsFullShadowError("linked semantic result is invalid")
for name in EVIDENCE_FILES: for name in EVIDENCE_FILES:
path = source / name path = source / name
proof = worker.get("files", {}).get(name, {}) proof = worker.get("files", {}).get(name, {})
@@ -131,6 +137,7 @@ def seal_m49_tgs_full_shadow(
"input_manifest_sha256": worker["input_manifest_sha256"], "input_manifest_sha256": worker["input_manifest_sha256"],
"config_sha256": worker["config_sha256"], "config_sha256": worker["config_sha256"],
"linked_visual_result_id": linked_visual_result_id, "linked_visual_result_id": linked_visual_result_id,
"linked_semantic_result_id": linked_semantic_result_id,
"files": {name: worker["files"][name]["sha256"] for name in EVIDENCE_FILES}, "files": {name: worker["files"][name]["sha256"] for name in EVIDENCE_FILES},
"authority": { "authority": {
"commands_enabled": False, "commands_enabled": False,
@@ -154,6 +161,7 @@ def seal_m49_tgs_full_shadow(
"source_session_id": "20260720T065719Z_viewer_live", "source_session_id": "20260720T065719Z_viewer_live",
"source_pack_sha256": worker["source_pack_sha256"], "source_pack_sha256": worker["source_pack_sha256"],
"linked_visual_result_id": linked_visual_result_id, "linked_visual_result_id": linked_visual_result_id,
"linked_semantic_result_id": linked_semantic_result_id,
}, },
"configuration": { "configuration": {
"profile_id": profile["profile_id"], "profile_id": profile["profile_id"],
@@ -211,6 +219,7 @@ def seal_m49_tgs_full_shadow(
"visual_review": { "visual_review": {
"instrument": "m4-canonical-reference-graph", "instrument": "m4-canonical-reference-graph",
"linked_visual_result_id": linked_visual_result_id, "linked_visual_result_id": linked_visual_result_id,
"linked_semantic_result_id": linked_semantic_result_id,
"frame_count": 4489, "frame_count": 4489,
"state_codes": profile["state_codes"], "state_codes": profile["state_codes"],
}, },
+72
View File
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import hashlib
import importlib.util import importlib.util
import json import json
import sys import sys
@@ -7,6 +8,7 @@ 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.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]
@@ -59,6 +61,76 @@ def test_full_shadow_worker_artifact_is_deterministic_and_cpu_only(tmp_path: Pat
assert "gpu_requested = $false" in runner_text assert "gpu_requested = $false" in runner_text
def test_full_shadow_seal_binds_visual_and_semantic_timelines(tmp_path: Path) -> None:
source = tmp_path / "worker"
source.mkdir()
files: dict[str, dict[str, object]] = {}
for name in (
"costmap-cell-centers-xy-m.npy",
"costmap-cell-indices-xy.npy",
"costmap-states.npy",
"costmap-z-bounds-m.npy",
"frames.ndjson",
):
payload = f"sealed:{name}\n".encode()
(source / name).write_bytes(payload)
files[name] = {
"bytes": len(payload),
"sha256": hashlib.sha256(payload).hexdigest(),
}
worker = {
"schema_version": "missioncore.m49-tgs-full-shadow-result/v1",
"status": "passed",
"source_pack_sha256": "a" * 64,
"input_manifest_sha256": "b" * 64,
"config_sha256": "c" * 64,
"timeline": {
"frame_count": 4489,
"available_lidar_frame_count": 3928,
"missing_lidar_frame_count": 561,
},
"point_accounting": {"unaccounted": 0},
"costmap": {"cell_size_m": 0.45, "radius_m": 12.0},
"performance": {},
"acceptance": {},
"files": files,
}
(source / "result.json").write_text(json.dumps(worker), encoding="utf-8")
(source / "worker-summary.json").write_text(json.dumps({
"schema_version": "missioncore.m49-tgs-full-shadow-worker-summary/v1",
"gpu_requested": False,
"aos_used": False,
"all_timeline_frames_accounted": True,
"all_eligible_points_accounted": True,
"canonical_triton_health": "healthy",
"canonical_triton_id": "triton",
"wall_seconds": 1.0,
}), encoding="utf-8")
profile = tmp_path / "profile.json"
profile.write_text(json.dumps({
"schema_version": "missioncore.m49-tgs-full-shadow-profile/v1",
"profile_id": "test",
"profile": {"history_seconds": 1.0},
"costmap": {"state_priority": ["NONGROUND_OCCUPIED"]},
"state_codes": {"UNOBSERVED": 0},
}), encoding="utf-8")
visual = "m4-threat-replay-" + "d" * 64
semantic = "e47-semantic-slam-" + "e" * 64
sealed = seal_m49_tgs_full_shadow(
source_root=source,
destination_root=tmp_path / "results",
profile_path=profile,
linked_visual_result_id=visual,
linked_semantic_result_id=semantic,
created_at_utc="2026-08-26T20:27:19Z",
)
assert sealed.report["source"]["linked_visual_result_id"] == visual
assert sealed.report["source"]["linked_semantic_result_id"] == semantic
assert sealed.manifest["identity"]["linked_semantic_result_id"] == semantic
def test_full_shadow_chunk_contract_keeps_missing_lidar_unobserved( def test_full_shadow_chunk_contract_keeps_missing_lidar_unobserved(
monkeypatch, monkeypatch,
tmp_path: Path, tmp_path: Path,