feat(perception): add E34 temporal occupied layer

This commit is contained in:
DCCONSTRUCTIONS
2026-07-27 16:55:40 +03:00
parent 95c6540691
commit 621084fcd6
22 changed files with 4732 additions and 104 deletions
@@ -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.421857292484.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.180.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. E31E34 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())