feat: qualify bounded K1 surface shadow

This commit is contained in:
DCCONSTRUCTIONS 2026-07-26 01:06:04 +03:00
parent 4c83e8a4e7
commit e6d5411bdd
8 changed files with 1370 additions and 14 deletions

View File

@ -158,6 +158,16 @@ in-band, above-plane and below-plane observations. On source frame `1254`,
localizing the heavy tail without naming an object or granting safety
authority.
The same estimator now runs behind
`missioncore.k1-local-surface-shadow-runtime/v1`, a capacity-two
latest-wins queue with a bounded diagnostic result ring. LAB E27 passed a
`14.995 s` RAVNOVES00 slice at recorded 1× pace: `143/143` available frames
were consumed, maximum queue depth was `1/2`, no frame was replaced, p95
processing latency was `14.777 ms`, and state, point classes, step candidates
and scalar results matched the immutable replay derivative exactly. This is a
recorded-source-paced execution gate only; physical K1 worker binding,
free-space, commands, navigation and safety authority remain unavailable.
The complete RELLIS-3D v1.1 release is now admitted there and its full
`2,413`-frame validation split is available in **Полигон → Датасеты**. The
sealed Current/Patchwork++ comparison rejected Patchwork++ for navigation:

View File

@ -4,7 +4,8 @@ Date: 2026-07-26
Status: accepted architecture plan; L0/L1 implemented; L2 diagnostic A/B
complete; full GOOSE and RELLIS qualification complete; L2.6d K1 replay
local-surface temporal qualification, operator triage and prior-plane residual
explainability implemented; bounded live shadow next
explainability implemented; L2.6e recorded-source-paced bounded shadow
qualified; physical K1 shadow next
Scope: passively received real-time K1 point/pose evidence, immutable replay and
future live shadow processing
Explicitly out of scope: K1 firmware modification, a new onboard exporter, new
@ -419,8 +420,11 @@ Dataset expansion is no longer the next gate.
aggregate p95.
- [ ] Complete the remaining qualification report with per-frame latency,
point age, obstacle preservation and memory growth.
- [ ] Replay the same profiles through a bounded latest-wins live-shadow queue;
no K1 command, navigation or safety authority is added.
- [x] Replay the same profile through a bounded latest-wins shadow queue at the
recorded 1× source rate; no K1 command, navigation or safety authority is
added.
- [ ] Bind that runtime to physical live K1 point/pose evidence through the
existing authenticated external-worker seam.
The implemented `missioncore.k1-local-surface/v1` derivative is reproducible
through `experiments/perception/run_k1_local_surface.py` and is exposed
@ -496,9 +500,27 @@ independent ground truth. Frame `1195` instead has only seven out-of-band cells
together, preserving its separate interpretation as a surface-regime
transition.
No fit threshold was changed after this review. The next gate is a bounded
latest-wins live-shadow queue using the same profile and evidence contract,
still without commands, free-space, navigation or safety authority.
No fit threshold was changed after this review.
The L2.6e runtime contract
`missioncore.k1-local-surface-shadow-runtime/v1` now copies and freezes one
decoded map-point/pose pair, runs the accepted estimator behind a
capacity-two latest-wins queue and retains only a bounded diagnostic result
ring. It has no command method, never turns missing points into free space and
publishes explicit false navigation/safety authority.
LAB E27 passed a `14.995 s` source-paced 1× RAVNOVES00 slice: all `143`
available frames were consumed, maximum queue depth was `1/2`, no work was
replaced and no processing failed. Processing latency was `12.261 ms` p50,
`14.777 ms` p95 and `33.135 ms` maximum. Every processed result matched the
immutable replay derivative: zero state, point-class or step-candidate
mismatches and zero scalar delta. The accepted result is
`k1-local-surface-shadow-04f14d8c580f74cbd5b0a452867563ebc6b3ef93d872129e4918680932253ab7`.
The next gate is not another replay tuning pass. It is an explicit bounded
LiDAR↔pose binder on the authenticated external worker stream followed by a
physical K1 shadow run, still without commands, free-space, navigation or
safety authority.
Exit: one immutable K1 session yields both a persistent reconstruction and a
bounded local world state without hard-coded terrain height or scanner-side
@ -521,7 +543,8 @@ independent gate without increasing unsafe false-free or false-dynamic output.
### L4 — live shadow integration
- [ ] Add a bounded LiDAR queue independent of camera cadence.
- [x] Add a provider-neutral bounded LiDAR local-surface queue independent of
camera cadence and qualify it at recorded 1× source pace.
- [ ] Run the accepted K1 local-surface/local-map profile on the NVIDIA worker.
- [ ] Fuse K1 geometric evidence with E26 camera evidence as independent
sources; a LiDAR-native detector remains optional.

View File

@ -93,8 +93,9 @@ Not implemented:
- no RELLIS ROS bag admission, continuous synchronized playback or production
promotion;
- no ray-cleared free-space or planner-authoritative rolling occupancy map.
- no bounded live-shadow execution of the accepted K1 local-surface profile
yet; the residual overlay remains replay-only and non-authoritative.
- the accepted K1 local-surface profile now passes a 15-second
recorded-source-paced bounded shadow gate; physical K1 worker binding is not
implemented yet, and the residual overlay remains non-authoritative.
## Product surface boundary

View File

@ -0,0 +1,97 @@
# LAB E27 — bounded K1 local-surface shadow qualification
Date: 2026-07-26
Status: **accepted for recorded-source-paced shadow diagnostic**
Authority: diagnostic only; commands, navigation and safety acceptance disabled
## 1. Question
Can the accepted K1 rolling local-surface profile execute through a real
bounded latest-wins worker loop at the recorded K1 source rate without queue
growth, frame replacement or divergence from the immutable replay result?
This lab does not ask whether the derived surface is ground truth or
planner-ready. It qualifies execution semantics only.
## 2. Fixed input
- session: `20260720T065719Z_viewer_live`;
- source pack:
`e10-lidar-pack-5da0396d32a27f9d1ca537cc2e8a371d386078d6f0dc71737b78620992af9625`;
- source artifact SHA-256:
`72aa73340b20fcfaa21b330ef5b93b975c14a70e2cd16a57752d9952ff05ad9a`;
- replay reference:
`k1-local-surface-628cd024775f02fea99765d1fb457efec2d5cc4d7371e56818dfe8e08c9b3b74`;
- reference logical-content SHA-256:
`b89a92887cedace9d3eab3e1490697fb6bc7fc16a2d1887f3fff7cb4547ceb14`;
- selection: source frames `0150`, `14.995 s`, 151 timeline entries and
143 available LiDAR frames;
- pace: recorded 1×;
- work queue: latest-wins, capacity 2;
- result ring: bounded to the 143-frame qualification selection.
The source pack and replay derivative were opened read-only. No scanner,
firmware, MQTT command or persistent reconstruction was changed.
## 3. Implemented runtime boundary
`missioncore.k1-local-surface-shadow-runtime/v1` accepts only an already
decoded map-frame point cloud and a compatible `T_map_from_sensor` pose. It:
1. copies and freezes the admitted point/pose pair;
2. publishes work into a bounded latest-wins queue;
3. runs the same robust rolling-cell and prior-only prediction profile used by
replay;
4. retains only a bounded diagnostic result ring;
5. reports observed surface, observed occupied-above-surface, negative
outliers, unverified step candidates, latency and freshness;
6. explicitly keeps absence-of-points distinct from free space.
The runtime has no command method and every result carries
`commands_enabled=false` and `navigation_or_safety_accepted=false`.
## 4. Result
Result:
`k1-local-surface-shadow-04f14d8c580f74cbd5b0a452867563ebc6b3ef93d872129e4918680932253ab7`
| Metric | Result |
| --- | ---: |
| Published / consumed | 143 / 143 |
| Latest-wins replacements | 0 |
| Maximum queue depth | 1 / 2 |
| Processing failures | 0 |
| Processing p50 / p95 / max | 12.261 / 14.777 / 33.135 ms |
| Result age p50 / p95 / max | 12.336 / 14.857 / 33.378 ms |
| Replay state mismatches | 0 |
| Point-class mismatches | 0 |
| Step-candidate mismatches | 0 |
| Maximum scalar delta | 0.0 |
All 143 results were valid for this selection. Queue accounting closed exactly:
`consumed + dropped_overflow = published`, final depth was zero and the worker
thread stopped cleanly.
## 5. Decision
The execution gate passes. The current CPU geometric profile is comfortably
inside the observed roughly 10 Hz K1 publication interval on this 15-second
slice, remains bounded and reproduces replay exactly when no work is replaced.
This result promotes the profile only from offline replay implementation to a
recorded-source-paced shadow candidate. It does not promote:
- the surface estimate to ground truth;
- observed occupancy to free-space evidence;
- step candidates to semantic curbs;
- the worker result to navigation or safety authority;
- the replay transport to a physical-live K1 gate.
## 6. Next gate
Connect the runtime to the existing authenticated external worker stream using
an explicit bounded LiDAR↔pose binder, then repeat at least 15 seconds against
a physical K1 acquisition. Measure source sequence gaps, pose-binding misses,
queue replacements, result age, memory slope and recovery across reconnect.
React remains a read-only status/review surface; it does not execute the
algorithm.

View File

@ -0,0 +1,365 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import math
import os
import time
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
from k1link.compute import (
DEFAULT_K1_LOCAL_SURFACE_PROFILE,
E10LidarFieldSource,
K1LocalSurfaceShadowInput,
K1LocalSurfaceShadowResult,
K1LocalSurfaceShadowRuntime,
K1LocalSurfaceV1,
)
from k1link.compute.lidar_local_surface import (
FRAME_FIT_FAILED,
FRAME_INSUFFICIENT_SURFACE,
FRAME_POSE_STALE,
FRAME_VALID,
)
QUALIFICATION_SCHEMA = "missioncore.k1-local-surface-shadow-qualification/v1"
def _arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Run the accepted K1 rolling-surface profile through a bounded "
"latest-wins replay shadow and compare processed frames to the "
"immutable replay derivative."
)
)
parser.add_argument("source_pack", type=Path)
parser.add_argument("reference_model", type=Path)
parser.add_argument("output_root", type=Path)
parser.add_argument("--start-frame", type=int, default=0)
parser.add_argument("--duration-seconds", type=float, default=15.0)
parser.add_argument("--pace-scale", type=float, default=1.0)
parser.add_argument("--queue-capacity", type=int, default=2)
return parser.parse_args()
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode()
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as stream:
while chunk := stream.read(1024 * 1024):
digest.update(chunk)
return digest.hexdigest()
def _distribution(values: list[float]) -> dict[str, float | int | None]:
if not values:
return {
"sample_count": 0,
"minimum": None,
"mean": None,
"p50": None,
"p95": None,
"maximum": None,
}
array = np.asarray(values, dtype=np.float64)
if not np.isfinite(array).all():
raise RuntimeError("shadow qualification latency is invalid")
return {
"sample_count": int(array.shape[0]),
"minimum": float(np.min(array)),
"mean": float(np.mean(array)),
"p50": float(np.percentile(array, 50)),
"p95": float(np.percentile(array, 95)),
"maximum": float(np.max(array)),
}
def _expected_state(model: K1LocalSurfaceV1, frame_index: int) -> str:
code = int(model.arrays["frame_failure_code"][frame_index])
return {
FRAME_VALID: "valid",
FRAME_POSE_STALE: "pose-stale",
FRAME_INSUFFICIENT_SURFACE: "insufficient-surface",
FRAME_FIT_FAILED: "fit-failed",
}[code]
def _maximum_scalar_delta(
result: K1LocalSurfaceShadowResult,
model: K1LocalSurfaceV1,
) -> float:
frame_index = result.frame_index
pairs = (
(result.sensor_height_m, "sensor_height_m"),
(result.slope_deg, "slope_deg"),
(result.roughness_m, "roughness_m"),
(result.confidence, "confidence"),
(result.surface_max_age_ms, "surface_max_age_ms"),
)
deltas = [
abs(float(value) - float(model.arrays[name][frame_index]))
for value, name in pairs
if value is not None
]
return max(deltas, default=0.0)
def _qualification(
source: E10LidarFieldSource,
model: K1LocalSurfaceV1,
*,
start_frame: int,
duration_seconds: float,
pace_scale: float,
queue_capacity: int,
) -> dict[str, Any]:
if (
model.identity.get("source_pack_id") != source.pack_id
or model.identity.get("source_pack_identity_sha256")
!= source.manifest.get("identity_sha256")
or model.identity.get("source_artifact_sha256")
!= source.manifest.get("artifact", {}).get("sha256")
or model.identity.get("profile") != DEFAULT_K1_LOCAL_SURFACE_PROFILE.to_dict()
):
raise RuntimeError("shadow qualification source/model binding is invalid")
if (
not 0 <= start_frame < source.frame_count
or not math.isfinite(duration_seconds)
or duration_seconds <= 0
or not math.isfinite(pace_scale)
or not 0 < pace_scale <= 10
or not 1 <= queue_capacity <= 8
):
raise RuntimeError("shadow qualification selection is invalid")
arrays = source.arrays
session_times = arrays["session_seconds"]
start_seconds = float(session_times[start_frame])
selected = [
frame_index
for frame_index in range(start_frame, source.frame_count)
if float(session_times[frame_index]) - start_seconds <= duration_seconds
]
if len(selected) < 2:
raise RuntimeError("shadow qualification selection is too short")
expected_available = [
frame_index for frame_index in selected if bool(arrays["sample_available"][frame_index])
]
runtime = K1LocalSurfaceShadowRuntime(
f"{source.identity['session_id']}-qualification",
queue_capacity=queue_capacity,
result_capacity=min(256, max(1, len(expected_available))),
)
wall_started = time.perf_counter()
offsets = arrays["cloud_offsets"]
try:
for frame_index in selected:
release_at = (
wall_started + (float(session_times[frame_index]) - start_seconds) * pace_scale
)
remaining = release_at - time.perf_counter()
if remaining > 0:
time.sleep(remaining)
if not bool(arrays["sample_available"][frame_index]):
continue
start = int(offsets[frame_index])
end = int(offsets[frame_index + 1])
runtime.publish(
K1LocalSurfaceShadowInput(
frame_index=frame_index,
source_frame_index=int(arrays["source_frame_indices"][frame_index]),
session_seconds=float(session_times[frame_index]),
pose_binding_age_ms=abs(float(arrays["pose_point_delta_ms"][frame_index])),
points_map=np.asarray(
arrays["cloud_points_map"][start:end],
dtype=np.float64,
),
position_map=np.asarray(
arrays["pose_positions_map"][frame_index],
dtype=np.float64,
),
published_monotonic_ns=time.monotonic_ns(),
)
)
runtime.close(timeout_seconds=30.0)
results = runtime.results()
runtime_snapshot = runtime.snapshot()
finally:
runtime.close()
state_mismatches = 0
point_class_mismatches = 0
step_candidate_mismatches = 0
maximum_scalar_delta = 0.0
for result in results:
frame_index = result.frame_index
state_mismatches += result.state != _expected_state(model, frame_index)
if result.valid:
start = int(offsets[frame_index])
end = int(offsets[frame_index + 1])
point_class_mismatches += int(
np.count_nonzero(result.point_class != model.arrays["point_class"][start:end])
)
step_candidate_mismatches += int(
np.count_nonzero(
result.point_step_candidate != model.arrays["point_step_candidate"][start:end]
)
)
maximum_scalar_delta = max(
maximum_scalar_delta,
_maximum_scalar_delta(result, model),
)
queue = runtime_snapshot["queue"]
accepted = (
int(queue["maximum_depth"]) <= queue_capacity
and int(queue["dropped_overflow"]) == 0
and int(queue["consumed"]) == len(expected_available)
and len(results) == len(expected_available)
and int(runtime_snapshot["results"]["failed"]) == 0
and state_mismatches == 0
and point_class_mismatches == 0
and step_candidate_mismatches == 0
and maximum_scalar_delta <= 1e-9
)
return {
"schema_version": QUALIFICATION_SCHEMA,
"identity": {
"source_pack_id": source.pack_id,
"source_pack_identity_sha256": source.manifest["identity_sha256"],
"source_artifact_sha256": source.manifest["artifact"]["sha256"],
"reference_model_id": model.model_id,
"reference_logical_content_sha256": model.identity["logical_content_sha256"],
"session_id": source.identity["session_id"],
"profile": DEFAULT_K1_LOCAL_SURFACE_PROFILE.to_dict(),
"selection": {
"start_frame": start_frame,
"end_frame": selected[-1],
"source_duration_seconds": (float(session_times[selected[-1]]) - start_seconds),
"selected_frames": len(selected),
"available_frames": len(expected_available),
},
"runtime": {
"queue_policy": "bounded-latest-wins",
"queue_capacity": queue_capacity,
"result_capacity": min(
256,
max(1, len(expected_available)),
),
"pace_scale": pace_scale,
},
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
},
"state": "accepted" if accepted else "rejected",
"accepted": accepted,
"ground_truth": False,
"metrics": {
"wall_elapsed_seconds": time.perf_counter() - wall_started,
"queue": queue,
"result_states": runtime_snapshot["results"]["state_counts"],
"processing_ms": _distribution([result.processing_ms for result in results]),
"result_age_ms": _distribution([result.result_age_ms for result in results]),
"replay_parity": {
"compared_frames": len(results),
"state_mismatches": state_mismatches,
"point_class_mismatches": point_class_mismatches,
"step_candidate_mismatches": step_candidate_mismatches,
"maximum_scalar_delta": maximum_scalar_delta,
},
},
"acceptance": {
"queue_bounded": int(queue["maximum_depth"]) <= queue_capacity,
"zero_latest_wins_replacements": int(queue["dropped_overflow"]) == 0,
"zero_processing_failures": (int(runtime_snapshot["results"]["failed"]) == 0),
"complete_processed_accounting": (
int(queue["consumed"]) == len(expected_available)
and len(results) == len(expected_available)
),
"replay_state_parity": state_mismatches == 0,
"replay_point_class_parity": point_class_mismatches == 0,
"replay_step_candidate_parity": step_candidate_mismatches == 0,
"replay_scalar_parity": maximum_scalar_delta <= 1e-9,
"navigation_or_safety_accepted": False,
},
"occupancy_policy": {
"absence_of_points_means_free": False,
"unknown_is_traversable": False,
},
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
},
}
def main() -> int:
arguments = _arguments()
source = E10LidarFieldSource(arguments.source_pack)
model = K1LocalSurfaceV1(arguments.reference_model)
try:
report = _qualification(
source,
model,
start_frame=arguments.start_frame,
duration_seconds=arguments.duration_seconds,
pace_scale=arguments.pace_scale,
queue_capacity=arguments.queue_capacity,
)
finally:
model.close()
source.close()
report_sha256 = hashlib.sha256(_canonical_json(report)).hexdigest()
result_id = f"k1-local-surface-shadow-{report_sha256}"
report["result_id"] = result_id
report["report_sha256"] = report_sha256
report["created_at_utc"] = datetime.now(UTC).isoformat()
output_root = arguments.output_root.expanduser().resolve()
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
output = output_root / result_id
if output.exists():
raise RuntimeError("shadow qualification result already exists")
staging = output_root / f".{result_id}.{os.getpid()}.incomplete"
staging.mkdir(mode=0o700, exist_ok=False)
try:
report_path = staging / "report.json"
report_path.write_bytes(_canonical_json(report))
os.replace(staging, output)
except BaseException:
if staging.exists():
for path in staging.iterdir():
path.unlink()
staging.rmdir()
raise
print(
json.dumps(
{
"result_id": result_id,
"output": str(output),
"state": report["state"],
"metrics": report["metrics"],
"authority": report["authority"],
},
ensure_ascii=False,
indent=2,
)
)
return 0 if report["accepted"] else 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -118,6 +118,14 @@ from .lidar_local_surface import (
build_k1_local_surface,
k1_local_surface_catalog_item,
)
from .lidar_local_surface_shadow import (
K1_LOCAL_SURFACE_SHADOW_FRAME_SCHEMA,
K1_LOCAL_SURFACE_SHADOW_SCHEMA,
K1LocalSurfaceShadowEstimator,
K1LocalSurfaceShadowInput,
K1LocalSurfaceShadowResult,
K1LocalSurfaceShadowRuntime,
)
from .lidar_replay import (
LIDAR_EQUIVALENCE_REPORT_SCHEMA,
LIDAR_QUALITY_REPORT_SCHEMA,
@ -213,6 +221,8 @@ __all__ = [
"K1_LOCAL_SURFACE_REVIEW_SCHEMA",
"K1_LOCAL_SURFACE_SCHEMA",
"K1_LOCAL_SURFACE_TIMELINE_SCHEMA",
"K1_LOCAL_SURFACE_SHADOW_FRAME_SCHEMA",
"K1_LOCAL_SURFACE_SHADOW_SCHEMA",
"LIDAR_FIELD_REVIEW_REPORT_SCHEMA",
"LIDAR_FIELD_REVIEW_SCHEMA",
"LIDAR_FIELD_REVIEW_WINDOW_SCHEMA",
@ -236,6 +246,10 @@ __all__ = [
"LidarGroundBenchmarkV1",
"LidarGroundError",
"K1LocalSurfaceProfile",
"K1LocalSurfaceShadowEstimator",
"K1LocalSurfaceShadowInput",
"K1LocalSurfaceShadowResult",
"K1LocalSurfaceShadowRuntime",
"K1LocalSurfaceV1",
"LidarReplayError",
"LidarReplayPackV2",

View File

@ -0,0 +1,681 @@
from __future__ import annotations
import math
import threading
import time
from collections import Counter, deque
from dataclasses import asdict, dataclass
from typing import Any, Final, Literal
import numpy as np
import numpy.typing as npt
from k1link.data_plane import DecodedPointCloudView, DecodedPoseView
from k1link.ground_segmentation import GroundSegmentationError as LidarGroundError
from .lidar_local_surface import (
DEFAULT_K1_LOCAL_SURFACE_PROFILE,
POINT_BELOW_SURFACE,
POINT_OCCUPIED,
POINT_SURFACE,
K1LocalSurfaceProfile,
_cloud_cell_observations,
_expire_cache,
_fit_surface,
_height_above_plane,
_local_cache_records,
_point_step_candidates,
_prediction_metrics,
_step_candidate_keys,
_update_cache,
)
from .live_perception import LatestWinsQueue
K1_LOCAL_SURFACE_SHADOW_SCHEMA: Final = "missioncore.k1-local-surface-shadow-runtime/v1"
K1_LOCAL_SURFACE_SHADOW_FRAME_SCHEMA: Final = "missioncore.k1-local-surface-shadow-frame/v1"
ShadowFrameState = Literal[
"valid",
"pose-stale",
"insufficient-surface",
"fit-failed",
]
@dataclass(frozen=True, slots=True)
class K1LocalSurfaceShadowInput:
"""One immutable map-point/pose pair admitted to passive shadow work."""
frame_index: int
source_frame_index: int
session_seconds: float
pose_binding_age_ms: float
points_map: npt.NDArray[np.float64]
position_map: npt.NDArray[np.float64]
published_monotonic_ns: int
def __post_init__(self) -> None:
points = np.array(self.points_map, dtype=np.float64, copy=True)
position = np.array(self.position_map, dtype=np.float64, copy=True)
if (
self.frame_index < 0
or self.source_frame_index < 0
or not math.isfinite(self.session_seconds)
or self.session_seconds < 0
or not math.isfinite(self.pose_binding_age_ms)
or self.pose_binding_age_ms < 0
or self.published_monotonic_ns < 0
or points.ndim != 2
or points.shape[1:] != (3,)
or position.shape != (3,)
or not np.isfinite(points).all()
or not np.isfinite(position).all()
):
raise LidarGroundError("K1 local-surface shadow input is invalid")
points.flags.writeable = False
position.flags.writeable = False
object.__setattr__(self, "points_map", points)
object.__setattr__(self, "position_map", position)
@classmethod
def from_views(
cls,
point_cloud: DecodedPointCloudView,
pose: DecodedPoseView,
) -> K1LocalSurfaceShadowInput:
"""Bind already-normalized views without interpreting vendor payloads."""
if (
point_cloud.frame_id != "map"
or pose.frame_id != "map"
or pose.child_frame_id != "sensor"
):
raise LidarGroundError(
"K1 local-surface shadow requires map points and map-from-sensor pose"
)
point_received_ns = point_cloud.context.received_monotonic_ns
pose_received_ns = pose.context.received_monotonic_ns
if point_received_ns is not None and pose_received_ns is not None:
pose_binding_age_ms = abs(point_received_ns - pose_received_ns) / 1_000_000
else:
pose_binding_age_ms = (
abs(point_cloud.context.captured_at_epoch_ns - pose.context.captured_at_epoch_ns)
/ 1_000_000
)
points = np.asarray(point_cloud.positions_xyz, dtype=np.float64).reshape((-1, 3))
position = np.asarray(pose.position_xyz, dtype=np.float64)
return cls(
frame_index=point_cloud.context.sequence,
source_frame_index=point_cloud.context.sequence,
session_seconds=point_cloud.context.captured_at_epoch_ns / 1_000_000_000,
pose_binding_age_ms=pose_binding_age_ms,
points_map=points,
position_map=position,
published_monotonic_ns=time.monotonic_ns(),
)
@dataclass(frozen=True, slots=True)
class K1LocalSurfaceShadowResult:
"""One bounded diagnostic result; it never describes free or safe space."""
frame_index: int
source_frame_index: int
session_seconds: float
state: ShadowFrameState
pose_binding_age_ms: float
surface_cell_count: int
surface_inlier_cell_count: int
plane_coefficients_map: tuple[float, float, float, float] | None
sensor_height_m: float | None
slope_deg: float | None
roughness_m: float | None
confidence: float | None
surface_max_age_ms: float | None
prediction_available: bool
prediction_cell_count: int
prediction_residual_p50_m: float | None
prediction_residual_p95_m: float | None
prediction_inlier_fraction: float | None
temporal_compared: bool
temporal_jump: bool
height_delta_m: float | None
slope_delta_deg: float | None
roughness_delta_m: float | None
surface_point_count: int
occupied_point_count: int
below_surface_point_count: int
step_candidate_point_count: int
processing_ms: float
result_age_ms: float
point_class: npt.NDArray[np.uint8]
point_height_m: npt.NDArray[np.float32]
point_step_candidate: npt.NDArray[np.uint8]
@property
def valid(self) -> bool:
return self.state == "valid"
def document(self) -> dict[str, object]:
return {
"schema_version": K1_LOCAL_SURFACE_SHADOW_FRAME_SCHEMA,
"frame_index": self.frame_index,
"source_frame_index": self.source_frame_index,
"session_seconds": self.session_seconds,
"state": self.state,
"valid": self.valid,
"surface": {
"pose_binding_age_ms": self.pose_binding_age_ms,
"cell_count": self.surface_cell_count,
"inlier_cell_count": self.surface_inlier_cell_count,
"plane_coefficients_map": (
list(self.plane_coefficients_map)
if self.plane_coefficients_map is not None
else None
),
"sensor_height_m": self.sensor_height_m,
"slope_deg": self.slope_deg,
"roughness_m": self.roughness_m,
"confidence": self.confidence,
"maximum_age_ms": self.surface_max_age_ms,
},
"prediction": {
"available": self.prediction_available,
"cell_count": self.prediction_cell_count,
"residual_p50_m": self.prediction_residual_p50_m,
"residual_p95_m": self.prediction_residual_p95_m,
"inlier_fraction": self.prediction_inlier_fraction,
"current_frame_excluded": True,
},
"temporal": {
"compared": self.temporal_compared,
"jump": self.temporal_jump,
"height_delta_m": self.height_delta_m,
"slope_delta_deg": self.slope_delta_deg,
"roughness_delta_m": self.roughness_delta_m,
},
"counts": {
"surface": self.surface_point_count,
"occupied_observed": self.occupied_point_count,
"below_surface": self.below_surface_point_count,
"step_candidate": self.step_candidate_point_count,
},
"delivery": {
"processing_ms": self.processing_ms,
"result_age_ms": self.result_age_ms,
},
"ground_truth": False,
"occupancy_policy": {
"absence_of_points_means_free": False,
"unknown_is_traversable": False,
},
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
},
}
class K1LocalSurfaceShadowEstimator:
"""Streaming-equivalent state for the accepted replay surface profile."""
def __init__(
self,
profile: K1LocalSurfaceProfile = DEFAULT_K1_LOCAL_SURFACE_PROFILE,
) -> None:
self.profile = profile
self._cache: dict[tuple[int, int], tuple[float, float]] = {}
self._previous_surface: tuple[float, float, float, float] | None = None
self._last_frame_index = -1
self._last_session_seconds = -math.inf
def process(
self,
value: K1LocalSurfaceShadowInput,
) -> K1LocalSurfaceShadowResult:
started_ns = time.monotonic_ns()
if (
value.frame_index <= self._last_frame_index
or value.session_seconds < self._last_session_seconds
):
raise LidarGroundError("K1 local-surface shadow input order is not monotonic")
self._last_frame_index = value.frame_index
self._last_session_seconds = value.session_seconds
if value.pose_binding_age_ms > self.profile.maximum_pose_binding_ms:
return self._result(
value,
started_ns=started_ns,
state="pose-stale",
)
cloud = value.points_map
position = value.position_map
local = np.sum((cloud[:, :2] - position[:2]) ** 2, axis=1) <= (
self.profile.local_radius_m**2
)
local_cloud = cloud[local]
_expire_cache(
self._cache,
value.session_seconds,
position,
self.profile,
)
_, prior_cell_points, _ = _local_cache_records(
self._cache,
position,
self.profile,
)
_, current_cell_points = _cloud_cell_observations(
local_cloud,
self.profile,
)
prediction = _prediction_metrics(
prior_cell_points,
current_cell_points,
position,
self.profile,
)
_update_cache(
self._cache,
local_cloud,
value.session_seconds,
self.profile,
)
cell_keys, cell_points, cell_times = _local_cache_records(
self._cache,
position,
self.profile,
)
prediction_available = prediction is not None
prediction_cell_count = prediction.cell_points.shape[0] if prediction is not None else 0
prediction_residual_p50_m = prediction.residual_p50_m if prediction is not None else None
prediction_residual_p95_m = prediction.residual_p95_m if prediction is not None else None
prediction_inlier_fraction = (
prediction.inlier_fraction(self.profile.surface_band_m)
if prediction is not None
else None
)
if cell_points.shape[0] < self.profile.minimum_surface_cells:
return self._result(
value,
started_ns=started_ns,
state="insufficient-surface",
surface_cell_count=cell_points.shape[0],
prediction_available=prediction_available,
prediction_cell_count=prediction_cell_count,
prediction_residual_p50_m=prediction_residual_p50_m,
prediction_residual_p95_m=prediction_residual_p95_m,
prediction_inlier_fraction=prediction_inlier_fraction,
)
fit = _fit_surface(cell_points, position, self.profile)
if fit is None:
return self._result(
value,
started_ns=started_ns,
state="fit-failed",
surface_cell_count=cell_points.shape[0],
prediction_available=prediction_available,
prediction_cell_count=prediction_cell_count,
prediction_residual_p50_m=prediction_residual_p50_m,
prediction_residual_p95_m=prediction_residual_p95_m,
prediction_inlier_fraction=prediction_inlier_fraction,
)
plane, inliers, residuals = fit
slope_deg = math.degrees(
math.atan2(
math.hypot(float(plane[0]), float(plane[1])),
float(plane[2]),
)
)
if not np.isfinite(slope_deg) or slope_deg > self.profile.maximum_slope_deg:
return self._result(
value,
started_ns=started_ns,
state="fit-failed",
surface_cell_count=cell_points.shape[0],
prediction_available=prediction_available,
prediction_cell_count=prediction_cell_count,
prediction_residual_p50_m=prediction_residual_p50_m,
prediction_residual_p95_m=prediction_residual_p95_m,
prediction_inlier_fraction=prediction_inlier_fraction,
)
heights = _height_above_plane(cloud, plane)
point_class = np.zeros(cloud.shape[0], dtype=np.uint8)
point_class[local & (np.abs(heights) <= self.profile.surface_band_m)] = POINT_SURFACE
point_class[
local
& (heights >= self.profile.obstacle_min_height_m)
& (heights <= self.profile.obstacle_max_height_m)
] = POINT_OCCUPIED
point_class[local & (heights < -self.profile.surface_band_m)] = POINT_BELOW_SURFACE
step_keys = _step_candidate_keys(
cell_keys,
cell_points,
plane,
self.profile,
)
point_step_candidate = _point_step_candidates(
cloud,
local,
heights,
step_keys,
self.profile,
)
point_height_m = np.zeros(cloud.shape[0], dtype=np.float32)
point_height_m[local] = heights[local].astype(np.float32)
sensor_height = float(_height_above_plane(position.reshape(1, 3), plane)[0])
roughness = float(np.median(np.abs(residuals[inliers])))
inlier_count = int(np.count_nonzero(inliers))
coverage = min(
1.0,
inlier_count / (self.profile.minimum_surface_cells * 3),
)
roughness_confidence = math.exp(-roughness / max(self.profile.surface_band_m, 1e-6))
pose_confidence = max(
0.0,
1.0 - value.pose_binding_age_ms / self.profile.maximum_pose_binding_ms,
)
confidence = float(
np.clip(
coverage * roughness_confidence * pose_confidence,
0.0,
1.0,
)
)
temporal_compared = False
temporal_jump = False
height_delta_m: float | None = None
slope_delta_deg: float | None = None
roughness_delta_m: float | None = None
if (
self._previous_surface is not None
and value.session_seconds - self._previous_surface[0] <= self.profile.surface_ttl_s
):
temporal_compared = True
height_delta_m = abs(sensor_height - self._previous_surface[1])
slope_delta_deg = abs(slope_deg - self._previous_surface[2])
roughness_delta_m = abs(roughness - self._previous_surface[3])
temporal_jump = (
height_delta_m > self.profile.temporal_height_jump_m
or slope_delta_deg > self.profile.temporal_slope_jump_deg
or roughness_delta_m > self.profile.temporal_roughness_jump_m
)
self._previous_surface = (
value.session_seconds,
sensor_height,
slope_deg,
roughness,
)
point_class.flags.writeable = False
point_height_m.flags.writeable = False
point_step_candidate.flags.writeable = False
return self._result(
value,
started_ns=started_ns,
state="valid",
surface_cell_count=cell_points.shape[0],
surface_inlier_cell_count=inlier_count,
plane_coefficients_map=(
float(plane[0]),
float(plane[1]),
float(plane[2]),
float(plane[3]),
),
sensor_height_m=sensor_height,
slope_deg=slope_deg,
roughness_m=roughness,
confidence=confidence,
surface_max_age_ms=max(
0.0,
(value.session_seconds - float(np.min(cell_times[inliers]))) * 1_000.0,
),
temporal_compared=temporal_compared,
temporal_jump=temporal_jump,
height_delta_m=height_delta_m,
slope_delta_deg=slope_delta_deg,
roughness_delta_m=roughness_delta_m,
surface_point_count=int(np.count_nonzero(point_class == POINT_SURFACE)),
occupied_point_count=int(np.count_nonzero(point_class == POINT_OCCUPIED)),
below_surface_point_count=int(np.count_nonzero(point_class == POINT_BELOW_SURFACE)),
step_candidate_point_count=int(np.count_nonzero(point_step_candidate)),
point_class=point_class,
point_height_m=point_height_m,
point_step_candidate=point_step_candidate,
prediction_available=prediction_available,
prediction_cell_count=prediction_cell_count,
prediction_residual_p50_m=prediction_residual_p50_m,
prediction_residual_p95_m=prediction_residual_p95_m,
prediction_inlier_fraction=prediction_inlier_fraction,
)
def _result(
self,
value: K1LocalSurfaceShadowInput,
*,
started_ns: int,
state: ShadowFrameState,
surface_cell_count: int = 0,
surface_inlier_cell_count: int = 0,
plane_coefficients_map: tuple[float, float, float, float] | None = None,
sensor_height_m: float | None = None,
slope_deg: float | None = None,
roughness_m: float | None = None,
confidence: float | None = None,
surface_max_age_ms: float | None = None,
prediction_available: bool = False,
prediction_cell_count: int = 0,
prediction_residual_p50_m: float | None = None,
prediction_residual_p95_m: float | None = None,
prediction_inlier_fraction: float | None = None,
temporal_compared: bool = False,
temporal_jump: bool = False,
height_delta_m: float | None = None,
slope_delta_deg: float | None = None,
roughness_delta_m: float | None = None,
surface_point_count: int = 0,
occupied_point_count: int = 0,
below_surface_point_count: int = 0,
step_candidate_point_count: int = 0,
point_class: npt.NDArray[np.uint8] | None = None,
point_height_m: npt.NDArray[np.float32] | None = None,
point_step_candidate: npt.NDArray[np.uint8] | None = None,
) -> K1LocalSurfaceShadowResult:
finished_ns = time.monotonic_ns()
if point_class is None:
point_class = np.zeros(value.points_map.shape[0], dtype=np.uint8)
point_class.flags.writeable = False
if point_height_m is None:
point_height_m = np.zeros(value.points_map.shape[0], dtype=np.float32)
point_height_m.flags.writeable = False
if point_step_candidate is None:
point_step_candidate = np.zeros(
value.points_map.shape[0],
dtype=np.uint8,
)
point_step_candidate.flags.writeable = False
return K1LocalSurfaceShadowResult(
frame_index=value.frame_index,
source_frame_index=value.source_frame_index,
session_seconds=value.session_seconds,
state=state,
pose_binding_age_ms=value.pose_binding_age_ms,
surface_cell_count=surface_cell_count,
surface_inlier_cell_count=surface_inlier_cell_count,
plane_coefficients_map=plane_coefficients_map,
sensor_height_m=sensor_height_m,
slope_deg=slope_deg,
roughness_m=roughness_m,
confidence=confidence,
surface_max_age_ms=surface_max_age_ms,
prediction_available=prediction_available,
prediction_cell_count=prediction_cell_count,
prediction_residual_p50_m=prediction_residual_p50_m,
prediction_residual_p95_m=prediction_residual_p95_m,
prediction_inlier_fraction=prediction_inlier_fraction,
temporal_compared=temporal_compared,
temporal_jump=temporal_jump,
height_delta_m=height_delta_m,
slope_delta_deg=slope_delta_deg,
roughness_delta_m=roughness_delta_m,
surface_point_count=surface_point_count,
occupied_point_count=occupied_point_count,
below_surface_point_count=below_surface_point_count,
step_candidate_point_count=step_candidate_point_count,
processing_ms=(finished_ns - started_ns) / 1_000_000,
result_age_ms=max(
0.0,
(finished_ns - value.published_monotonic_ns) / 1_000_000,
),
point_class=point_class,
point_height_m=point_height_m,
point_step_candidate=point_step_candidate,
)
class K1LocalSurfaceShadowRuntime:
"""One bounded latest-wins worker with a bounded diagnostic result ring."""
def __init__(
self,
session_id: str,
*,
profile: K1LocalSurfaceProfile = DEFAULT_K1_LOCAL_SURFACE_PROFILE,
queue_capacity: int = 2,
result_capacity: int = 8,
) -> None:
if (
not session_id
or len(session_id) > 160
or not 1 <= queue_capacity <= 8
or not 1 <= result_capacity <= 256
):
raise LidarGroundError("K1 local-surface shadow runtime bounds are invalid")
self.session_id = session_id
self.profile = profile
self._queue = LatestWinsQueue[K1LocalSurfaceShadowInput](queue_capacity)
self._result_capacity = result_capacity
self._results: deque[K1LocalSurfaceShadowResult] = deque(maxlen=result_capacity)
self._result_dropped = 0
self._processed = 0
self._failed = 0
self._state_counts: Counter[str] = Counter()
self._last_error: str | None = None
self._inflight = False
self._condition = threading.Condition()
self._closed = False
self._estimator = K1LocalSurfaceShadowEstimator(profile)
self._thread = threading.Thread(
target=self._run,
name=f"k1-local-surface-shadow-{session_id}",
daemon=True,
)
self._thread.start()
def publish(self, value: K1LocalSurfaceShadowInput) -> None:
with self._condition:
if self._closed:
raise RuntimeError("K1 local-surface shadow runtime is closed")
self._queue.publish(value)
def publish_views(
self,
point_cloud: DecodedPointCloudView,
pose: DecodedPoseView,
) -> None:
self.publish(K1LocalSurfaceShadowInput.from_views(point_cloud, pose))
def wait_until_idle(self, timeout_seconds: float) -> bool:
if timeout_seconds < 0:
raise ValueError("K1 local-surface shadow wait timeout is invalid")
deadline = time.monotonic() + timeout_seconds
while True:
queue_snapshot = self._queue.snapshot()
with self._condition:
accounted = (
queue_snapshot.consumed + queue_snapshot.dropped_overflow
== queue_snapshot.published
)
if accounted and queue_snapshot.depth == 0 and not self._inflight:
return True
remaining = deadline - time.monotonic()
if remaining <= 0:
return False
self._condition.wait(timeout=remaining)
def close(self, *, timeout_seconds: float = 30.0) -> None:
if timeout_seconds <= 0:
raise ValueError("K1 local-surface shadow close timeout is invalid")
with self._condition:
already_closed = self._closed
self._closed = True
if not already_closed:
self._queue.close()
self._thread.join(timeout=timeout_seconds)
if self._thread.is_alive():
raise RuntimeError("K1 local-surface shadow worker did not stop")
def results(self) -> tuple[K1LocalSurfaceShadowResult, ...]:
with self._condition:
return tuple(self._results)
def snapshot(self) -> dict[str, Any]:
queue_snapshot = self._queue.snapshot()
with self._condition:
latest = self._results[-1] if self._results else None
return {
"schema_version": K1_LOCAL_SURFACE_SHADOW_SCHEMA,
"mode": "live-shadow-diagnostic-only",
"session_id": self.session_id,
"profile": self.profile.to_dict(),
"queue_policy": "bounded-latest-wins",
"queue": asdict(queue_snapshot),
"results": {
"capacity": self._result_capacity,
"depth": len(self._results),
"published": self._processed,
"dropped_ring_overflow": self._result_dropped,
"state_counts": dict(sorted(self._state_counts.items())),
"failed": self._failed,
"latest": latest.document() if latest is not None else None,
},
"last_error": self._last_error,
"closed": self._closed and not self._thread.is_alive(),
"ground_truth": False,
"occupancy_policy": {
"absence_of_points_means_free": False,
"unknown_is_traversable": False,
},
"authority": {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
},
}
def _run(self) -> None:
while True:
value = self._queue.take_next()
if value is None:
with self._condition:
self._condition.notify_all()
return
with self._condition:
self._inflight = True
try:
result = self._estimator.process(value)
except (LidarGroundError, ValueError, np.linalg.LinAlgError) as exc:
with self._condition:
self._failed += 1
self._last_error = type(exc).__name__
else:
with self._condition:
if len(self._results) == self._result_capacity:
self._result_dropped += 1
self._results.append(result)
self._processed += 1
self._state_counts[result.state] += 1
finally:
with self._condition:
self._inflight = False
self._condition.notify_all()

View File

@ -2,9 +2,11 @@ from __future__ import annotations
import hashlib
import json
import time
from pathlib import Path
import numpy as np
import pytest
from fastapi import APIRouter
from fastapi.routing import APIRoute
@ -12,6 +14,8 @@ from k1link.compute import (
E10_LIDAR_PACK_SCHEMA,
E10LidarFieldSource,
K1LocalSurfaceProfile,
K1LocalSurfaceShadowInput,
K1LocalSurfaceShadowRuntime,
K1LocalSurfaceV1,
build_k1_local_surface,
)
@ -51,9 +55,7 @@ def _source_pack(root: Path) -> Path:
zz = 0.04 * xx - 0.015 * yy + 0.008 * np.sin(xx * 2 + frame_index)
ground = np.column_stack((xx.ravel(), yy.ravel(), zz.ravel()))
obstacle_xy = ground[::13, :2]
obstacle_z = (
0.04 * obstacle_xy[:, 0] - 0.015 * obstacle_xy[:, 1] + 0.75
)
obstacle_z = 0.04 * obstacle_xy[:, 0] - 0.015 * obstacle_xy[:, 1] + 0.75
obstacle = np.column_stack((obstacle_xy, obstacle_z))
cloud = np.concatenate((ground, obstacle)).astype("<f4")
clouds.append(cloud)
@ -188,8 +190,9 @@ def test_k1_local_surface_is_dynamic_source_bound_and_read_only(
assert len(evidence["cell_points_xyz_m"]) == detail["prediction"]["cell_count"]
assert len(evidence["cell_signed_residual_m"]) == detail["prediction"]["cell_count"]
assert len(evidence["cell_inlier"]) == detail["prediction"]["cell_count"]
assert sum(evidence["cell_inlier"]) / detail["prediction"]["cell_count"] == (
detail["prediction"]["inlier_fraction"]
assert (
sum(evidence["cell_inlier"]) / detail["prediction"]["cell_count"]
== (detail["prediction"]["inlier_fraction"])
)
assert detail["temporal"]["compared"] is True
assert detail["authority"]["commands_enabled"] is False
@ -239,3 +242,165 @@ def test_k1_local_surface_is_dynamic_source_bound_and_read_only(
assert review["review_profile_id"] == "missioncore-local-surface-attention/v1"
assert review["access"] == "read-only"
assert str(tmp_path) not in repr(review)
def _shadow_input(
source: E10LidarFieldSource,
frame_index: int,
) -> K1LocalSurfaceShadowInput:
offsets = source.arrays["cloud_offsets"]
start = int(offsets[frame_index])
end = int(offsets[frame_index + 1])
points = np.asarray(
source.arrays["cloud_points_map"][start:end],
dtype=np.float64,
).copy()
position = np.asarray(
source.arrays["pose_positions_map"][frame_index],
dtype=np.float64,
).copy()
points.flags.writeable = False
position.flags.writeable = False
return K1LocalSurfaceShadowInput(
frame_index=frame_index,
source_frame_index=int(source.arrays["source_frame_indices"][frame_index]),
session_seconds=float(source.arrays["session_seconds"][frame_index]),
pose_binding_age_ms=abs(float(source.arrays["pose_point_delta_ms"][frame_index])),
points_map=points,
position_map=position,
published_monotonic_ns=time.monotonic_ns(),
)
def test_k1_local_surface_shadow_matches_replay_and_stays_non_authoritative(
tmp_path: Path,
) -> None:
source_path = _source_pack(tmp_path / "source")
profile = K1LocalSurfaceProfile(
profile_id="synthetic-shadow-local-surface/v1",
local_radius_m=5.0,
cell_size_m=0.5,
surface_ttl_s=0.5,
minimum_surface_cells=12,
)
source = E10LidarFieldSource(source_path)
try:
output = build_k1_local_surface(
source,
tmp_path / "models",
profile=profile,
)
finally:
source.close()
source = E10LidarFieldSource(source_path)
model = K1LocalSurfaceV1(output)
runtime = K1LocalSurfaceShadowRuntime(
"synthetic-shadow",
profile=profile,
queue_capacity=2,
result_capacity=16,
)
try:
published = []
for frame_index in range(source.frame_count):
if not bool(source.arrays["sample_available"][frame_index]):
continue
runtime.publish(_shadow_input(source, frame_index))
assert runtime.wait_until_idle(2.0)
published.append(frame_index)
runtime.close()
results = runtime.results()
assert [item.frame_index for item in results] == published
offsets = source.arrays["cloud_offsets"]
for result in results:
frame_index = result.frame_index
start = int(offsets[frame_index])
end = int(offsets[frame_index + 1])
if bool(model.arrays["frame_valid"][frame_index]):
assert result.state == "valid"
assert result.sensor_height_m == pytest.approx(
float(model.arrays["sensor_height_m"][frame_index]),
abs=1e-12,
)
assert result.slope_deg == pytest.approx(
float(model.arrays["slope_deg"][frame_index]),
abs=1e-12,
)
assert result.roughness_m == pytest.approx(
float(model.arrays["roughness_m"][frame_index]),
abs=1e-12,
)
assert result.confidence == pytest.approx(
float(model.arrays["confidence"][frame_index]),
abs=1e-12,
)
assert np.array_equal(
result.point_class,
model.arrays["point_class"][start:end],
)
assert np.array_equal(
result.point_step_candidate,
model.arrays["point_step_candidate"][start:end],
)
else:
assert result.state == "pose-stale"
snapshot = runtime.snapshot()
assert snapshot["queue"]["capacity"] == 2
assert snapshot["queue"]["published"] == len(published)
assert snapshot["queue"]["consumed"] == len(published)
assert snapshot["queue"]["dropped_overflow"] == 0
assert snapshot["results"]["failed"] == 0
assert snapshot["occupancy_policy"]["absence_of_points_means_free"] is False
assert snapshot["authority"]["commands_enabled"] is False
assert snapshot["authority"]["navigation_or_safety_accepted"] is False
assert snapshot["closed"] is True
finally:
runtime.close()
source.close()
model.close()
def test_k1_local_surface_shadow_overload_is_bounded_and_latest_wins(
tmp_path: Path,
) -> None:
source_path = _source_pack(tmp_path / "source")
source = E10LidarFieldSource(source_path)
runtime = K1LocalSurfaceShadowRuntime(
"synthetic-overload",
profile=K1LocalSurfaceProfile(
profile_id="synthetic-shadow-overload/v1",
local_radius_m=5.0,
cell_size_m=0.5,
minimum_surface_cells=12,
),
queue_capacity=1,
result_capacity=2,
)
try:
template = _shadow_input(source, 0)
for frame_index in range(200):
runtime.publish(
K1LocalSurfaceShadowInput(
frame_index=frame_index,
source_frame_index=10_000 + frame_index,
session_seconds=10.0 + frame_index * 0.1,
pose_binding_age_ms=4.0,
points_map=template.points_map,
position_map=template.position_map,
published_monotonic_ns=time.monotonic_ns(),
)
)
runtime.close()
snapshot = runtime.snapshot()
queue = snapshot["queue"]
assert queue["maximum_depth"] <= queue["capacity"] == 1
assert queue["dropped_overflow"] > 0
assert queue["consumed"] + queue["dropped_overflow"] == queue["published"]
assert snapshot["results"]["depth"] <= 2
assert runtime.results()[-1].frame_index == 199
assert snapshot["results"]["latest"]["frame_index"] == 199
assert snapshot["authority"]["commands_enabled"] is False
finally:
runtime.close()
source.close()