docs(studio-3d): 阶段 6 交付说明(正确性与失效状态)+ 接口骨架同步
- What:
1. `docs/architecture/studio-3d-stages.md` 新增「阶段 6:运行正确性与失效状态(批次 A)—— 已完成」:
做(epoch/字段级来源/设备几何身份/加载请求号/工具与重编包)、验收表格(含两份探针的
before/after 与全量计数)、"这一阶段抓到的" 7 条、兼容说明 6 条;文首进度改成阶段 6 已完成、
7/8/9 未开始;依赖关系图扩展为 6→7/8→9。
2. `docs/architecture/studio-3d-contracts.pyi` 与本阶段的契约改动同步:`SceneState` 加 `'stale'`;
`SurfaceFieldFrame`/`RenderFrame` 加 `computed_frame_id`;`ExpectedGeometry` 加 `source_geometry_id`
并注明米制 id 何时为空;`SceneAssets` 加 `source_geometry_id`/`compatible_sensor_model`/
`compatible_hardware_revisions`;`ThreeDCoordinator.__init__` 加 `clock`/`stale_grace_s`,新增
`note_field_failure()`/`show_rest()`/`epoch`/`last_presented()`。
- Why: 规格 §13 要求每阶段同时交出实现、测试、输入样例、失败样例、兼容说明与平台诊断报告;阶段 6 的
契约改动(`SceneState`、两个 `computed_frame_id`、`ExpectedGeometry`/`SceneAssets` 的新身份字段、
协调器新参数)必须先落到接口骨架里,否则下一阶段会照着过期文档实现。审查报告本身不修改
(`_review_scene3d_20260914/` 保持原样),结论与反号证据写进本仓文档。
- Verification:
· 阶段末全量复核(与本提交同一工作树):apps/studio 234 项 0 失败(14 跳过 = offscreen 下的真 GL 用例,
用 `QT_QPA_PLATFORM=windows` 单独跑为 14 项 0 失败 0 跳过);shared 316 项 0 失败 1 跳过;
`ui_check` 587 项 0 失败;`locale_check` 0 缺失(zh/en 各 398 键,仅有键集合相同);
`validate_scene.py` 对重编发布包 `source geometry recorded 46a03a5fbac1eace` 等全项 ok。
· 复现证据:`.repro/probe_stage6_r1.py`(R1/R7-b 反号)、`.repro/probe_stage6_device.py`
(R3:同尺寸不同映射被拒、表面不同被拒、设备不声明 → unverified),输出 JSON 与扫描件留在 `.repro/`。
- Not in this change: 不改代码(本提交只动文档与接口骨架);阶段 7/8/9 的计划条目(二维后端统一、
回放入口收口、能力控件、限速与真实统计、保护包密钥注入、ARM 与 2 小时门禁)尚未开始;
真机上"算法链失败时 `VRAW` 是否也过期"这一条仍待实测(风险项已写进阶段 6 小节)。
This commit is contained in:
@@ -8,7 +8,7 @@ by NDArray. See studio-3d-design.md for threading and validation requirements.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Generic, Literal, Mapping, Protocol, TypeAlias, TypeVar
|
||||
from typing import Callable, Generic, Literal, Mapping, Protocol, TypeAlias, TypeVar
|
||||
from concurrent.futures import Executor
|
||||
|
||||
import numpy as np
|
||||
@@ -28,7 +28,7 @@ Role: TypeAlias = Literal['sensing', 'housing', 'reference']
|
||||
Quality: TypeAlias = Literal['calibrated', 'approximate', 'display_only']
|
||||
Profile: TypeAlias = Literal['desktop33', 'gles30']
|
||||
OfferResult: TypeAlias = Literal['accepted', 'replaced', 'stale', 'disabled', 'invalid']
|
||||
SceneState: TypeAlias = Literal['disabled', 'loading', 'ready', 'live', 'no_data', 'suspended', 'unavailable']
|
||||
SceneState: TypeAlias = Literal['disabled', 'loading', 'ready', 'live', 'no_data', 'suspended', 'unavailable', 'stale']
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -75,6 +75,7 @@ class SurfaceFieldFrame:
|
||||
displacement_sensor_m: F32 | None # [H,W,3]; base displacement BEFORE display gain.
|
||||
displacement_quality: Quality | None
|
||||
diagnostic: Diagnostic | None
|
||||
computed_frame_id: int | None # Frame the array was computed for; None = unstated.
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -118,9 +119,10 @@ class CameraPreset:
|
||||
class ExpectedGeometry:
|
||||
sensor_model: str
|
||||
hardware_revision: str
|
||||
geometry_id: str
|
||||
geometry_id: str # Metre-scaled shape identity; '' when the device cannot state it.
|
||||
field_domain_id: str
|
||||
shape_hw: tuple[int, int]
|
||||
source_geometry_id: str # Unit-free surface identity a device CAN reproduce.
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -133,6 +135,9 @@ class SceneAssets:
|
||||
binding: SurfaceBinding # v1 supports exactly one sensing part.
|
||||
camera_presets: tuple[CameraPreset, ...]
|
||||
bounds_sensor_m: F32 # [2,3]: min, max.
|
||||
source_geometry_id: str # Unit-free source geometry this asset was built from.
|
||||
compatible_sensor_model: str
|
||||
compatible_hardware_revisions: tuple[str, ...] # '*' means any revision.
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -172,6 +177,7 @@ class RenderFrame:
|
||||
valid: Mask # [N].
|
||||
scalar_descriptor: FieldDescriptor
|
||||
diagnostics: tuple[Diagnostic, ...]
|
||||
computed_frame_id: int | None # Carried through so the display side re-checks it.
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -345,17 +351,23 @@ class Sensor3DWidget(QOpenGLWidget):
|
||||
|
||||
class ThreeDCoordinator:
|
||||
def __init__(self, widget: Sensor3DWidget, composer: SceneComposer,
|
||||
executor: Executor, max_fps: int = 30) -> None: ...
|
||||
executor: Executor, max_fps: int = 30, *,
|
||||
clock: Callable[[], float] = ..., stale_grace_s: float = 1.0) -> None: ...
|
||||
def activate(self, assets: SceneAssets, source_id: str) -> int: ...
|
||||
def submit(self, frame: SurfaceFieldFrame) -> OfferResult: ...
|
||||
def note_field_failure(self, item: Diagnostic | None = None) -> None: ...
|
||||
def tick(self) -> None: ...
|
||||
def set_visible(self, visible: bool) -> None: ...
|
||||
def set_settings(self, settings: DisplaySettings) -> None: ...
|
||||
def show_rest(self) -> None: ...
|
||||
def close(self) -> None: ...
|
||||
@property
|
||||
def state(self) -> SceneState: ...
|
||||
@property
|
||||
def generation(self) -> int: ...
|
||||
@property
|
||||
def epoch(self) -> int: ...
|
||||
def last_presented(self) -> tuple[FrameIdentity, float] | None: ...
|
||||
|
||||
|
||||
class ThreeDPanel(QWidget):
|
||||
|
||||
Reference in New Issue
Block a user