feat(perception): qualify E35 degradation recovery

This commit is contained in:
DCCONSTRUCTIONS
2026-07-27 17:53:59 +03:00
parent 621084fcd6
commit b894945344
21 changed files with 3985 additions and 17 deletions
@@ -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 A0A8.
The next critical-path gate is A9/E36: run the frozen E32E35 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())