feat(perception): add E34 temporal occupied layer
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
# LAB E34 — bounded short-TTL occupied/unknown temporal layer
|
||||
|
||||
Date: 2026-07-27
|
||||
Status: accepted for source-scoped diagnostic/shadow use; dynamic class, free
|
||||
space, planner, navigation, safety and command authority are unavailable
|
||||
Immutable result:
|
||||
`e34-temporal-occupied-8d9abb3f2cc072cfdbb16cc4e55798e05c35a0abe0b8f691096770e091573a73`
|
||||
|
||||
## Objective and architecture stage
|
||||
|
||||
E34 closes the occupied-layer half of A8. It asks whether accepted E32
|
||||
hit-backed geometry can form a bounded local temporal model without changing
|
||||
raw/map evidence or polluting persistent reconstruction.
|
||||
|
||||
The result is a separate `lidar-local-map/short-ttl-hit-only/v1` derivative. It makes
|
||||
`current`, `held` and `expired` states explicit, spatially re-associates
|
||||
frame-local geometry-only components, preserves camera identity only as source
|
||||
provenance and never converts missing mapped points into free space.
|
||||
|
||||
## Immutable inputs and predeclared profile
|
||||
|
||||
- E32:
|
||||
`e32-track-geometry-a14ca0e7fb3850ca0dfa3c41634e1b490a2d58ab74d101afc6d6921fbdb0e6fd`.
|
||||
- Accepted E33 timing/accounting envelope:
|
||||
`e33-worker-shadow-05cc0bb264410fd49536df90e94067ac39731aff0322a8873700d40008a8bb3a`.
|
||||
- Source: RAVNOVES00 / `20260720T065719Z_viewer_live`.
|
||||
- Frames: `4,489`.
|
||||
- Timeline: `35.421857292–484.044857292 s`.
|
||||
- Coordinate frame: accepted E32 `map`.
|
||||
- Profile:
|
||||
`missioncore.e34-temporal-occupied-profile/v1`,
|
||||
`e34-short-ttl-hit-only-occupied-unknown/v1`.
|
||||
|
||||
The profile and ADR 0027 were written before the first full replay:
|
||||
|
||||
- hit-only voxel size `0.45 m`;
|
||||
- occupied TTL `0.75 s`;
|
||||
- maximum `256` active components;
|
||||
- maximum `4,096` cells per component;
|
||||
- geometry-only re-association gap `0.35 s`;
|
||||
- centroid gate `0.9 m`;
|
||||
- neighbor-expanded voxel overlap minimum `0.05`;
|
||||
- no free space, dynamic class or persistent-map mutation;
|
||||
- navigation, safety and command authority false.
|
||||
|
||||
No threshold or bound was changed after inspecting either run.
|
||||
|
||||
## Method and algorithms
|
||||
|
||||
For every E32 frame the E34 layer:
|
||||
|
||||
1. reconstructs the exact validated `TrackGeometryFrame` and memory-mapped
|
||||
`PointSlab`;
|
||||
2. consumes only geometries whose metric basis is `current-points`;
|
||||
3. voxelizes their exclusive map-frame point rows at `0.45 m`;
|
||||
4. preserves an exact camera owner across frames without transferring semantic
|
||||
ownership to E34;
|
||||
5. re-associates geometry-only observations one-to-one using age, centroid and
|
||||
neighbor-expanded voxel overlap;
|
||||
6. publishes current hit-backed components as `current/occupied`;
|
||||
7. publishes missing but not expired prior evidence as `held/unknown`;
|
||||
8. emits an `expired/unknown` tombstone on the independent layer deadline and
|
||||
removes the cells from active occupancy;
|
||||
9. checks a global map jump only when at least four components support one
|
||||
coherent translation; scalar nearest-neighbor distance alone is
|
||||
insufficient;
|
||||
10. records compact cell rows, complete per-frame state, track-history tails,
|
||||
expiration events, review samples, metrics, acceptance and artifact
|
||||
digests.
|
||||
|
||||
Bounds fail closed. Components and cells are never silently truncated or
|
||||
evicted. Unknown camera-only/conflict/held observations without current E32
|
||||
points cannot create occupied cells.
|
||||
|
||||
## Failed first run and architecture corrections
|
||||
|
||||
The first immutable result
|
||||
`e34-temporal-occupied-1bab293c1867100e172923b189f8273c47eacca3dd179fd54573200906affd7c`
|
||||
was rejected by two predeclared gates:
|
||||
|
||||
- seven scalar nearest-neighbor map-jump candidates;
|
||||
- maximum frame-driven expiry delay `0.283 s`, above the `0.25 s` gate.
|
||||
|
||||
The result was not overwritten and the profile was not relaxed.
|
||||
|
||||
Inspection showed that the jump implementation did not yet implement the
|
||||
word “global”: the flagged component displacement directions were
|
||||
incoherent, and frames 1762/3190 contained an exact camera anchor moving only
|
||||
`0.18–0.24 m`. The implementation was corrected to require a single coherent
|
||||
translation with residual no larger than the already fixed `0.45 m` voxel.
|
||||
|
||||
The expiry failure exposed a real source gap: frame 1732 at
|
||||
`208.513857292 s` is followed by frame 1733 at `208.846857292 s`. A
|
||||
frame-driven state machine cannot emit a `0.75 s` TTL event during that
|
||||
`0.333 s` gap. Expiration was therefore moved to an independent logical layer
|
||||
deadline. The exact event time is retained separately from the later replay
|
||||
materialization time. This is an architecture correction, not a larger TTL.
|
||||
|
||||
## Accepted result
|
||||
|
||||
Result:
|
||||
`e34-temporal-occupied-8d9abb3f2cc072cfdbb16cc4e55798e05c35a0abe0b8f691096770e091573a73`.
|
||||
|
||||
| Measure | Result | Gate |
|
||||
| --- | ---: | ---: |
|
||||
| processed frames | 4,489 / 4,489 | complete |
|
||||
| E32 point rows consumed | 2,119,302 / 2,119,302 | exact |
|
||||
| peak active components | 37 | ≤ 256 |
|
||||
| peak cells/component | 324 | ≤ 4,096 |
|
||||
| current / held cell rows | 834,478 / 531,581 | hit-backed / stale-unknown |
|
||||
| free cell rows | 0 | 0 |
|
||||
| component continuity | 82.15% | descriptive, not GT |
|
||||
| geometry-only re-association | 81.78% | descriptive, not GT |
|
||||
| components with geometry re-association | 2,366 | descriptive |
|
||||
| maximum observed component span | 13.394 s | descriptive |
|
||||
| maximum held age | 0.750 s | ≤ 0.750 s |
|
||||
| logical expiry delay | 0.000 s | ≤ 0.250 s |
|
||||
| maximum replay materialization delay | 0.283 s | retained source-gap evidence |
|
||||
| coherent global map-jump candidates | 0 | 0 |
|
||||
| frame processing p95 | 1.483 ms | diagnostic runtime |
|
||||
|
||||
The layer created 4,912 temporal components: 1,027 camera-provenance and 3,885
|
||||
geometry-only. It recorded 5,168 exact camera associations, 17,433 spatial
|
||||
geometry re-associations, 39,664 held publications and 4,895 expiration
|
||||
events. The peak active state remained 37 components.
|
||||
|
||||
All predeclared acceptance requirements pass. Raw/map evidence, E32/E33 and
|
||||
persistent reconstruction retain their exact artifact identities.
|
||||
|
||||
## Product materialization
|
||||
|
||||
The accepted result is exposed through the path-free read-only endpoint
|
||||
`GET /api/v1/laboratory/e34/results?limit=1` and through a separate
|
||||
`LAB E34 · temporal occupied/unknown` entry in the laboratory contour.
|
||||
|
||||
The UI uses the fixed `missioncore.laboratory-report/v1` anatomy:
|
||||
|
||||
- compact human-readable task, method, principal result and limitation;
|
||||
- a domain 3D viewer with the admitted 3D/plan and fullscreen controls;
|
||||
- 27 bounded review checkpoints for current, held and expired states;
|
||||
- explicit current/held/expired counts, source availability and session time;
|
||||
- a fixed result block with proved, not proved and decision statements.
|
||||
|
||||
The central laboratory workspace did not gain another experiment-specific
|
||||
layout branch. E31–E34 discovery and rendering are isolated in the advanced
|
||||
laboratory feature registry; the E34 visualizer is a domain renderer inside
|
||||
the existing laboratory evidence surface. No new generic Design Guideline
|
||||
component or visual language was introduced.
|
||||
|
||||
## Interpretation and limitations
|
||||
|
||||
E34 proves:
|
||||
|
||||
- bounded current/held/expired occupied/unknown state for this source;
|
||||
- lossless consumption of accepted E32 point rows;
|
||||
- deterministic geometry-only temporal identity;
|
||||
- independent TTL expiry;
|
||||
- no hidden free-space claim or persistent-map write;
|
||||
- enough compact evidence to inspect history and expiration.
|
||||
|
||||
E34 does not prove:
|
||||
|
||||
- detector accuracy or human ground truth;
|
||||
- a dynamic/static class;
|
||||
- ray-cleared free space, traversability, TSDF or ESDF;
|
||||
- second-source generalization;
|
||||
- correct behavior under injected source loss, delay, drops or offset;
|
||||
- planner, navigation, safety or command authority.
|
||||
|
||||
The `0.283 s` replay materialization gap is deliberately retained. E35 must
|
||||
exercise source-independent deadlines and recovery under deterministic loss,
|
||||
staleness, delay, bounded drops and timing offsets.
|
||||
|
||||
## Decision and next stage
|
||||
|
||||
E34 is accepted as a source-scoped diagnostic/shadow temporal occupied/unknown
|
||||
layer. A8 remains open only for E35.
|
||||
|
||||
The next critical-path work is E35 deterministic degradation and recovery
|
||||
using immutable derived replay variants. Missing evidence must become
|
||||
camera-only, geometry-only, stale or unknown; it must never become guessed
|
||||
class, false free space or hidden success.
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"schema_version": "missioncore.e34-temporal-occupied-profile/v1",
|
||||
"profile_id": "e34-short-ttl-hit-only-occupied-unknown/v1",
|
||||
"expected_e32_result_id": "e32-track-geometry-a14ca0e7fb3850ca0dfa3c41634e1b490a2d58ab74d101afc6d6921fbdb0e6fd",
|
||||
"expected_e33_result_id": "e33-worker-shadow-05cc0bb264410fd49536df90e94067ac39731aff0322a8873700d40008a8bb3a",
|
||||
"layer": {
|
||||
"coordinate_frame": "map",
|
||||
"voxel_size_m": 0.45,
|
||||
"occupied_ttl_seconds": 0.75,
|
||||
"maximum_active_components": 256,
|
||||
"maximum_cells_per_component": 4096
|
||||
},
|
||||
"association": {
|
||||
"geometry_maximum_gap_seconds": 0.35,
|
||||
"geometry_maximum_centroid_distance_m": 0.9,
|
||||
"geometry_minimum_voxel_overlap_fraction": 0.05,
|
||||
"geometry_neighbor_radius_cells": 1
|
||||
},
|
||||
"map_frame_jump": {
|
||||
"maximum_adjacent_gap_seconds": 0.25,
|
||||
"minimum_matched_components": 4,
|
||||
"minimum_median_displacement_m": 1.5,
|
||||
"minimum_p25_displacement_m": 0.9
|
||||
},
|
||||
"acceptance": {
|
||||
"maximum_expiry_delay_seconds": 0.25,
|
||||
"maximum_map_frame_jump_candidates": 0,
|
||||
"require_complete_frame_accounting": true,
|
||||
"require_exact_upstream_artifact_identity": true,
|
||||
"require_no_free_space_publication": true,
|
||||
"require_bounded_state": true
|
||||
},
|
||||
"policy": {
|
||||
"hit_only_occupied": true,
|
||||
"absence_of_points_means_free": false,
|
||||
"expired_components_remain_occupied": false,
|
||||
"dynamic_class_available": false,
|
||||
"free_space_available": false,
|
||||
"persistent_reconstruction_mutation_allowed": false
|
||||
},
|
||||
"authority": {
|
||||
"commands_enabled": false,
|
||||
"navigation_or_safety_accepted": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the immutable E34 short-TTL occupied/unknown temporal layer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.compute.e34_temporal_occupied_replay import (
|
||||
build_e34_temporal_occupied_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("--profile", type=Path, required=True)
|
||||
parser.add_argument("--output-root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
result = build_e34_temporal_occupied_replay(
|
||||
e32_result_root=args.e32_result,
|
||||
e33_result_root=args.e33_result,
|
||||
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