feat(perception): qualify E35 degradation recovery
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
# LAB E35 — deterministic degradation and recovery
|
||||
|
||||
Date: 2026-07-27
|
||||
Status: accepted for source-scoped diagnostic/shadow use; persistent
|
||||
reconstruction, planner, navigation, safety and command authority remain
|
||||
unavailable
|
||||
Immutable result:
|
||||
`e35-degradation-recovery-82bdbd5c5bfde6d932737f077153c3a8472c993c343fcbe8a207c39bfa2a6288`
|
||||
|
||||
## Objective and architecture stage
|
||||
|
||||
E35 closes the degradation half of A8. It asks whether the accepted
|
||||
E32 TrackGeometry, E33 delivery envelope and E34 temporal occupied/unknown
|
||||
layer fail safely and recover explicitly when one required source or timing
|
||||
property degrades.
|
||||
|
||||
The experiment does not mutate or replace any accepted input. It derives six
|
||||
complete deterministic variants from the exact RAVNOVES00 replay, applies one
|
||||
bounded transformation in each variant and replays every variant through a
|
||||
fresh E34 state machine with its frozen profile.
|
||||
|
||||
## Immutable inputs and predeclared profile
|
||||
|
||||
- E32:
|
||||
`e32-track-geometry-a14ca0e7fb3850ca0dfa3c41634e1b490a2d58ab74d101afc6d6921fbdb0e6fd`.
|
||||
- E33:
|
||||
`e33-worker-shadow-05cc0bb264410fd49536df90e94067ac39731aff0322a8873700d40008a8bb3a`.
|
||||
- E34:
|
||||
`e34-temporal-occupied-8d9abb3f2cc072cfdbb16cc4e55798e05c35a0abe0b8f691096770e091573a73`.
|
||||
- Source: RAVNOVES00 / `20260720T065719Z_viewer_live`.
|
||||
- Frames: `4,489` per variant, `26,934` terminal variant-frame outcomes.
|
||||
- Profile:
|
||||
`missioncore.e35-degradation-profile/v1`,
|
||||
`e35-six-channel-degradation-recovery/v1`.
|
||||
- Frozen E34 layer: `0.45 m` voxels, `0.75 s` occupied TTL and the accepted
|
||||
component/association bounds.
|
||||
|
||||
The profile and ADR 0028 were written before the full replay. Each fault
|
||||
occupies a distinct 60-frame interval:
|
||||
|
||||
1. camera observations unavailable;
|
||||
2. LiDAR points unavailable;
|
||||
3. pose age `0.5 s`, above the admitted `0.1 s` binding;
|
||||
4. result delay `1.0 s`, above the `0.75 s` TTL, with discard policy;
|
||||
5. every third input frame dropped;
|
||||
6. camera↔LiDAR offset `250 ms`, above the admitted `100 ms` gate.
|
||||
|
||||
Acceptance required complete accounting, an explicit injection journal, all
|
||||
variants recovered within `0.25 s`, and zero hidden success, false free-space,
|
||||
semantic claims without camera, metric rows without LiDAR/current pose, `agree`
|
||||
claims under timing mismatch and late-result reintroduction.
|
||||
|
||||
## Method and algorithms
|
||||
|
||||
For every source frame and every scenario E35:
|
||||
|
||||
1. reconstructs the exact validated E32 `TrackGeometryFrame` and `PointSlab`;
|
||||
2. records a digest of the original frame;
|
||||
3. applies one pure frame-local transformation only inside the declared
|
||||
interval;
|
||||
4. removes semantic identity on camera loss while retaining hit-backed points
|
||||
only as `geometry-only`;
|
||||
5. removes map-frame point rows on LiDAR loss or stale pose while retaining
|
||||
camera observations only as non-metric `camera-only`;
|
||||
6. discards late frames and prevents them from returning after their logical
|
||||
deadline;
|
||||
7. journals every dropped frame and passes intervening bounded-drop frames in
|
||||
original order;
|
||||
8. splits mismatched camera and LiDAR evidence into separate `camera-only` and
|
||||
`geometry-only` claims so the pair cannot publish `agree`;
|
||||
9. feeds the transformed frame into an independent frozen E34 temporal layer;
|
||||
10. records terminal layer state, current/held/expired counts, recovery,
|
||||
transformation digests, policy and authority.
|
||||
|
||||
No missing point set is treated as free space. No transform writes into the
|
||||
persistent reconstruction or any accepted upstream artifact.
|
||||
|
||||
## Accepted result
|
||||
|
||||
Result:
|
||||
`e35-degradation-recovery-82bdbd5c5bfde6d932737f077153c3a8472c993c343fcbe8a207c39bfa2a6288`.
|
||||
|
||||
| Scenario | Injected frames | Explicit drops | Recovery | Unsafe claims |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| camera loss | 60 | 0 | 0.086 s | 0 semantics |
|
||||
| LiDAR loss | 60 | 0 | 0.100 s | 0 metric rows |
|
||||
| pose staleness | 60 | 0 | 0.101 s | 0 metric rows |
|
||||
| delayed frames | 60 | 60 | 0.088 s | 0 late returns |
|
||||
| bounded drop | 60 | 20 | 0.101 s | 0 hidden success |
|
||||
| timing offset | 60 | 0 | 0.102 s | 0 `agree` |
|
||||
|
||||
All six variants processed `4,489 / 4,489` frames. The immutable artifacts
|
||||
contain `26,934` terminal outcomes and `360` explicit injection records.
|
||||
Maximum recovery was `0.102 s`, below the predeclared `0.25 s` gate.
|
||||
|
||||
Across all variants:
|
||||
|
||||
- hidden success frames: `0`;
|
||||
- false free cell rows: `0`;
|
||||
- semantic claims during camera loss: `0`;
|
||||
- metric rows during LiDAR loss or stale pose: `0`;
|
||||
- `agree` claims during timing offset: `0`;
|
||||
- late results reintroduced: `0`;
|
||||
- upstream artifact changes: `0`;
|
||||
- variant-frame processing p95: `1.080 ms`.
|
||||
|
||||
## Product materialization
|
||||
|
||||
The accepted result is exposed through the path-free read-only endpoint
|
||||
`GET /api/v1/laboratory/e35/results?limit=1` and a separate
|
||||
`LAB E35 · degradation recovery` entry in the laboratory contour.
|
||||
|
||||
The UI extends the fixed `missioncore.laboratory-report/v1` template. It shows:
|
||||
|
||||
- the task, immutable method and human-readable conclusion;
|
||||
- six selectable fault scenarios;
|
||||
- a compact before/during/after recovery timeline;
|
||||
- current, held and expired spatial evidence in the existing laboratory
|
||||
viewer with admitted 3D/plan and fullscreen controls;
|
||||
- explicit fault action, channel state, recovery time, unsafe-claim count and
|
||||
acceptance decision;
|
||||
- proved, not proved and next-gate statements.
|
||||
|
||||
E35 does not add a new generic control, a separate page anatomy or a
|
||||
run-specific visual language.
|
||||
|
||||
## Interpretation and limitations
|
||||
|
||||
E35 proves, on this immutable source and frozen profile:
|
||||
|
||||
- each declared channel/timing failure loses unsupported capability explicitly;
|
||||
- the temporal layer ages prior evidence as held/expired unknown rather than
|
||||
inventing free space;
|
||||
- late evidence is discarded and cannot silently return;
|
||||
- nominal current evidence returns on the next source frame after every fault;
|
||||
- accounting and authority remain closed.
|
||||
|
||||
E35 does not prove:
|
||||
|
||||
- detector accuracy or human ground truth;
|
||||
- transfer to another capture, device or mount;
|
||||
- ray-cleared free space, traversability, TSDF or ESDF;
|
||||
- planner, navigation, safety or command acceptance.
|
||||
|
||||
## Decision and next stage
|
||||
|
||||
E35 is accepted and closes A8. Workstream A has completed A0–A8.
|
||||
|
||||
The next critical-path gate is A9/E36: run the frozen E32–E35 profile without
|
||||
retuning on an eligible second mounted real source. The source catalog must be
|
||||
audited first. If no recording contains the required camera, LiDAR, pose,
|
||||
time, calibration and mount identities, E36 remains explicitly blocked; no
|
||||
empty LAB, fabricated result or new capture is created by default.
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"schema_version": "missioncore.e35-degradation-profile/v1",
|
||||
"profile_id": "e35-six-channel-degradation-recovery/v1",
|
||||
"expected_e32_result_id": "e32-track-geometry-a14ca0e7fb3850ca0dfa3c41634e1b490a2d58ab74d101afc6d6921fbdb0e6fd",
|
||||
"expected_e33_result_id": "e33-worker-shadow-05cc0bb264410fd49536df90e94067ac39731aff0322a8873700d40008a8bb3a",
|
||||
"expected_e34_result_id": "e34-temporal-occupied-8d9abb3f2cc072cfdbb16cc4e55798e05c35a0abe0b8f691096770e091573a73",
|
||||
"scenarios": [
|
||||
{
|
||||
"scenario_id": "camera-loss",
|
||||
"kind": "camera-loss",
|
||||
"frame_start": 600,
|
||||
"frame_end": 659,
|
||||
"parameters": {
|
||||
"drop_camera_observations": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"scenario_id": "lidar-loss",
|
||||
"kind": "lidar-loss",
|
||||
"frame_start": 1200,
|
||||
"frame_end": 1259,
|
||||
"parameters": {
|
||||
"drop_lidar_points": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"scenario_id": "pose-staleness",
|
||||
"kind": "pose-staleness",
|
||||
"frame_start": 1800,
|
||||
"frame_end": 1859,
|
||||
"parameters": {
|
||||
"pose_age_seconds": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"scenario_id": "delayed-frames",
|
||||
"kind": "delayed-frames",
|
||||
"frame_start": 2400,
|
||||
"frame_end": 2459,
|
||||
"parameters": {
|
||||
"delay_seconds": 1.0,
|
||||
"late_result_policy": "discard"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scenario_id": "bounded-drop",
|
||||
"kind": "bounded-drop",
|
||||
"frame_start": 3000,
|
||||
"frame_end": 3059,
|
||||
"parameters": {
|
||||
"drop_every_nth_frame": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"scenario_id": "timing-offset",
|
||||
"kind": "timing-offset",
|
||||
"frame_start": 3600,
|
||||
"frame_end": 3659,
|
||||
"parameters": {
|
||||
"camera_lidar_offset_ms": 250
|
||||
}
|
||||
}
|
||||
],
|
||||
"acceptance": {
|
||||
"expected_scenario_count": 6,
|
||||
"maximum_recovery_seconds": 0.25,
|
||||
"maximum_hidden_success_frames": 0,
|
||||
"maximum_false_free_rows": 0,
|
||||
"maximum_semantic_claims_during_camera_loss": 0,
|
||||
"maximum_metric_rows_during_lidar_or_pose_loss": 0,
|
||||
"maximum_late_results_reintroduced": 0,
|
||||
"require_complete_variant_frame_accounting": true,
|
||||
"require_exact_upstream_artifact_identity": true,
|
||||
"require_explicit_injection_journal": true,
|
||||
"require_every_variant_to_recover": true
|
||||
},
|
||||
"policy": {
|
||||
"absence_of_points_means_free": false,
|
||||
"late_results_may_reenter": false,
|
||||
"timing_mismatch_may_publish_agree": false,
|
||||
"pose_stale_points_may_publish_map_occupancy": false,
|
||||
"persistent_reconstruction_mutation_allowed": false
|
||||
},
|
||||
"authority": {
|
||||
"commands_enabled": false,
|
||||
"navigation_or_safety_accepted": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the immutable E35 degradation and recovery qualification."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.compute.e35_degradation_replay import (
|
||||
build_e35_degradation_replay,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--e32-result", type=Path, required=True)
|
||||
parser.add_argument("--e33-result", type=Path, required=True)
|
||||
parser.add_argument("--e34-result", type=Path, required=True)
|
||||
parser.add_argument("--e34-profile", type=Path, required=True)
|
||||
parser.add_argument("--profile", type=Path, required=True)
|
||||
parser.add_argument("--output-root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
result = build_e35_degradation_replay(
|
||||
e32_result_root=args.e32_result,
|
||||
e33_result_root=args.e33_result,
|
||||
e34_result_root=args.e34_result,
|
||||
e34_profile_path=args.e34_profile,
|
||||
profile_path=args.profile,
|
||||
output_root=args.output_root,
|
||||
)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"result_id": result.result_id,
|
||||
"result_root": str(result.result_root),
|
||||
"accepted": result.accepted,
|
||||
"metrics": result.report["metrics"],
|
||||
"rejection_reasons": result.report["acceptance"][
|
||||
"rejection_reasons"
|
||||
],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0 if result.accepted else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user