fix(control-station): align lidar viewer and expose polygon
This commit is contained in:
parent
c01712c30d
commit
881e97312b
|
|
@ -47,7 +47,6 @@ import type {
|
||||||
import { useRecordedSessionAdmission } from "./core/observation/useRecordedSessionAdmission";
|
import { useRecordedSessionAdmission } from "./core/observation/useRecordedSessionAdmission";
|
||||||
import { useWorkspaceLayoutProfile } from "./core/observation/useWorkspaceLayoutProfile";
|
import { useWorkspaceLayoutProfile } from "./core/observation/useWorkspaceLayoutProfile";
|
||||||
import { resolvePolygonRunRoute } from "./core/polygon/runArchive";
|
import { resolvePolygonRunRoute } from "./core/polygon/runArchive";
|
||||||
import { usePolygonWorkerCapability } from "./core/polygon/usePolygonWorkerCapability";
|
|
||||||
import {
|
import {
|
||||||
OBSERVATION_WORKSPACE_ID,
|
OBSERVATION_WORKSPACE_ID,
|
||||||
OBSERVATION_WORKSPACE_LAYOUT_VERSION,
|
OBSERVATION_WORKSPACE_LAYOUT_VERSION,
|
||||||
|
|
@ -55,7 +54,7 @@ import {
|
||||||
} from "./core/observation/workspaceLayout";
|
} from "./core/observation/workspaceLayout";
|
||||||
import {
|
import {
|
||||||
rootById,
|
rootById,
|
||||||
rootsForCapabilities,
|
roots,
|
||||||
workspaceById,
|
workspaceById,
|
||||||
workspacesForRoot,
|
workspacesForRoot,
|
||||||
type RootId,
|
type RootId,
|
||||||
|
|
@ -151,7 +150,6 @@ function mergeViewerSettings(
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const runtime = useMissionRuntime();
|
const runtime = useMissionRuntime();
|
||||||
const { selection } = useDevicePluginHost();
|
const { selection } = useDevicePluginHost();
|
||||||
const polygonWorker = usePolygonWorkerCapability();
|
|
||||||
const polygonRunRoute = useMemo(
|
const polygonRunRoute = useMemo(
|
||||||
() => resolvePolygonRunRoute(typeof window === "undefined" ? "" : window.location.search),
|
() => resolvePolygonRunRoute(typeof window === "undefined" ? "" : window.location.search),
|
||||||
[],
|
[],
|
||||||
|
|
@ -199,10 +197,7 @@ export default function App() {
|
||||||
const polygonRunRouteOpenedRef = useRef(false);
|
const polygonRunRouteOpenedRef = useRef(false);
|
||||||
|
|
||||||
const currentRoot = rootById(activeRoot);
|
const currentRoot = rootById(activeRoot);
|
||||||
const visibleRoots = useMemo(
|
const visibleRoots = roots;
|
||||||
() => rootsForCapabilities({ polygonWorkerAvailable: polygonWorker.available }),
|
|
||||||
[polygonWorker.available],
|
|
||||||
);
|
|
||||||
const activeDefinition = workspaceById(workspace.activeView);
|
const activeDefinition = workspaceById(workspace.activeView);
|
||||||
const spatialWorkspaceActive = Boolean(
|
const spatialWorkspaceActive = Boolean(
|
||||||
workspace.contentOpen && activeDefinition?.kind === "spatial",
|
workspace.contentOpen && activeDefinition?.kind === "spatial",
|
||||||
|
|
|
||||||
|
|
@ -796,14 +796,6 @@ export function rootById(id: RootId | null): RootDefinition | null {
|
||||||
return id ? roots.find((root) => root.id === id) ?? null : null;
|
return id ? roots.find((root) => root.id === id) ?? null : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rootsForCapabilities({
|
|
||||||
polygonWorkerAvailable,
|
|
||||||
}: {
|
|
||||||
polygonWorkerAvailable: boolean;
|
|
||||||
}): RootDefinition[] {
|
|
||||||
return roots.filter((root) => root.id !== "polygon" || polygonWorkerAvailable);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function workspaceById(id: string | null): WorkspaceDefinition | null {
|
export function workspaceById(id: string | null): WorkspaceDefinition | null {
|
||||||
return id ? workspaces.find((workspace) => workspace.id === id) ?? null : null;
|
return id ? workspaces.find((workspace) => workspace.id === id) ?? null : null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1441,9 +1441,8 @@
|
||||||
.lidar-field-cloud {
|
.lidar-field-cloud {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border: 1px solid rgb(255 255 255 / 0.09);
|
|
||||||
border-radius: 0.9rem;
|
border-radius: 0.9rem;
|
||||||
background: #071018;
|
background: #06070a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lidar-field-camera {
|
.lidar-field-camera {
|
||||||
|
|
@ -1461,7 +1460,6 @@
|
||||||
.lidar-field-camera > .lidar-field-stage__label,
|
.lidar-field-camera > .lidar-field-stage__label,
|
||||||
.lidar-field-cloud > header {
|
.lidar-field-cloud > header {
|
||||||
min-height: 3.45rem;
|
min-height: 3.45rem;
|
||||||
border-bottom: 1px solid rgb(255 255 255 / 0.08);
|
|
||||||
padding: 0.66rem 0.72rem;
|
padding: 0.66rem 0.72rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1487,7 +1485,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
border-top: 1px solid rgb(255 255 255 / 0.08);
|
|
||||||
padding: 0.52rem 0.7rem;
|
padding: 0.52rem 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1548,7 +1545,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 0.8rem;
|
margin-top: 0.8rem;
|
||||||
border-radius: 0.9rem;
|
border-radius: 0.9rem;
|
||||||
background: #071018;
|
background: #06070a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lidar-fallback-review > header {
|
.lidar-fallback-review > header {
|
||||||
|
|
@ -1557,7 +1554,6 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.8rem;
|
gap: 0.8rem;
|
||||||
border-bottom: 1px solid rgb(255 255 255 / 0.08);
|
|
||||||
padding: 0.65rem 0.75rem;
|
padding: 0.65rem 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1569,7 +1565,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.lidar-fallback-review > footer {
|
.lidar-fallback-review > footer {
|
||||||
border-top: 1px solid rgb(255 255 255 / 0.08);
|
|
||||||
padding: 0.58rem 0.75rem;
|
padding: 0.58rem 0.75rem;
|
||||||
color: var(--nodedc-text-muted);
|
color: var(--nodedc-text-muted);
|
||||||
font-size: 0.58rem;
|
font-size: 0.58rem;
|
||||||
|
|
@ -1646,11 +1641,8 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
margin-top: 0.85rem;
|
margin-top: 0.85rem;
|
||||||
border: 1px solid rgb(74 215 255 / 0.18);
|
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
background:
|
background: #06070a;
|
||||||
radial-gradient(circle at 18% 0%, rgb(56 124 255 / 0.12), transparent 34%),
|
|
||||||
rgb(4 12 19 / 0.72);
|
|
||||||
padding: 0.85rem;
|
padding: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1716,9 +1708,9 @@
|
||||||
.lidar-ground-modes button,
|
.lidar-ground-modes button,
|
||||||
.lidar-ground-frame-control button,
|
.lidar-ground-frame-control button,
|
||||||
.lidar-ground-scene__toolbar button {
|
.lidar-ground-scene__toolbar button {
|
||||||
border: 1px solid var(--station-hairline);
|
border: 0;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgb(255 255 255 / 0.035);
|
background: transparent;
|
||||||
padding: 0.4rem 0.62rem;
|
padding: 0.4rem 0.62rem;
|
||||||
color: var(--nodedc-text-secondary);
|
color: var(--nodedc-text-secondary);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
|
|
@ -1730,9 +1722,16 @@
|
||||||
.lidar-ground-modes button[data-active="true"],
|
.lidar-ground-modes button[data-active="true"],
|
||||||
.lidar-ground-frame-control button:hover:not(:disabled),
|
.lidar-ground-frame-control button:hover:not(:disabled),
|
||||||
.lidar-ground-scene__toolbar button:hover {
|
.lidar-ground-scene__toolbar button:hover {
|
||||||
border-color: rgb(74 215 255 / 0.48);
|
background: rgb(255 255 255 / 0.11);
|
||||||
background: rgb(74 215 255 / 0.1);
|
|
||||||
color: var(--nodedc-text-primary);
|
color: var(--nodedc-text-primary);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lidar-ground-modes button:focus-visible,
|
||||||
|
.lidar-ground-frame-control button:focus-visible,
|
||||||
|
.lidar-ground-scene__toolbar button:focus-visible {
|
||||||
|
background: rgb(255 255 255 / 0.14);
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lidar-ground-frame-control button {
|
.lidar-ground-frame-control button {
|
||||||
|
|
@ -1751,16 +1750,15 @@
|
||||||
|
|
||||||
.lidar-ground-frame-control input {
|
.lidar-ground-frame-control input {
|
||||||
width: min(16rem, 24vw);
|
width: min(16rem, 24vw);
|
||||||
accent-color: #4ad7ff;
|
accent-color: #f2f2f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lidar-ground-scene {
|
.lidar-ground-scene {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 30rem;
|
min-height: 30rem;
|
||||||
border: 1px solid rgb(255 255 255 / 0.09);
|
|
||||||
border-radius: 0.9rem;
|
border-radius: 0.9rem;
|
||||||
background: #071018;
|
background: #06070a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lidar-ground-scene__viewport {
|
.lidar-ground-scene__viewport {
|
||||||
|
|
@ -1782,15 +1780,15 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
border: 1px solid rgb(255 255 255 / 0.08);
|
border: 0;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgb(5 13 21 / 0.82);
|
background: rgb(10 11 14 / 0.78);
|
||||||
padding: 0.28rem;
|
padding: 0.28rem;
|
||||||
backdrop-filter: blur(14px);
|
backdrop-filter: blur(14px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lidar-ground-scene__toolbar button {
|
.lidar-ground-scene__toolbar button {
|
||||||
background: rgb(74 215 255 / 0.08);
|
background: rgb(255 255 255 / 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lidar-ground-scene__error {
|
.lidar-ground-scene__error {
|
||||||
|
|
@ -1808,9 +1806,8 @@
|
||||||
place-content: center;
|
place-content: center;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
gap: 0.55rem;
|
gap: 0.55rem;
|
||||||
border: 1px solid var(--station-hairline);
|
|
||||||
border-radius: 0.9rem;
|
border-radius: 0.9rem;
|
||||||
background: #071018;
|
background: #06070a;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,12 +60,13 @@ function frameColors(
|
||||||
const candidateAssigned = frame.masks.candidateAssigned[index] === 1;
|
const candidateAssigned = frame.masks.candidateAssigned[index] === 1;
|
||||||
if (mode === "intensity") {
|
if (mode === "intensity") {
|
||||||
const intensity = (frame.intensity0To255?.[index] ?? 96) / 255;
|
const intensity = (frame.intensity0To255?.[index] ?? 96) / 255;
|
||||||
|
const neutral = 0.16 + intensity * 0.8;
|
||||||
setRgb(
|
setRgb(
|
||||||
colors,
|
colors,
|
||||||
offset,
|
offset,
|
||||||
0.12 + intensity * 0.74,
|
neutral,
|
||||||
0.24 + intensity * 0.68,
|
neutral,
|
||||||
0.34 + intensity * 0.6,
|
neutral,
|
||||||
);
|
);
|
||||||
} else if (mode === "current") {
|
} else if (mode === "current") {
|
||||||
setRgb(
|
setRgb(
|
||||||
|
|
@ -131,7 +132,7 @@ export function LidarGroundPointCloud({
|
||||||
}
|
}
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
renderer.setClearColor(0x071018, 0.96);
|
renderer.setClearColor(0x06070a, 1);
|
||||||
renderer.domElement.setAttribute(
|
renderer.domElement.setAttribute(
|
||||||
"aria-label",
|
"aria-label",
|
||||||
"Интерактивное облако LiDAR",
|
"Интерактивное облако LiDAR",
|
||||||
|
|
@ -139,7 +140,7 @@ export function LidarGroundPointCloud({
|
||||||
host.prepend(renderer.domElement);
|
host.prepend(renderer.domElement);
|
||||||
|
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
const fog = new THREE.FogExp2(0x071018, 0.035);
|
const fog = new THREE.FogExp2(0x06070a, 0.035);
|
||||||
scene.fog = fog;
|
scene.fog = fog;
|
||||||
fogRef.current = fog;
|
fogRef.current = fog;
|
||||||
const camera = new THREE.PerspectiveCamera(48, 1, 0.01, 1_000);
|
const camera = new THREE.PerspectiveCamera(48, 1, 0.01, 1_000);
|
||||||
|
|
@ -172,7 +173,7 @@ export function LidarGroundPointCloud({
|
||||||
materialRef.current = material;
|
materialRef.current = material;
|
||||||
scene.add(new THREE.Points(geometry, material));
|
scene.add(new THREE.Points(geometry, material));
|
||||||
|
|
||||||
const grid = new THREE.GridHelper(10, 40, 0x71963d, 0x26312a);
|
const grid = new THREE.GridHelper(10, 40, 0x454846, 0x252725);
|
||||||
gridRef.current = grid;
|
gridRef.current = grid;
|
||||||
const gridMaterials = Array.isArray(grid.material)
|
const gridMaterials = Array.isArray(grid.material)
|
||||||
? grid.material
|
? grid.material
|
||||||
|
|
|
||||||
|
|
@ -378,7 +378,7 @@ export function PolygonRoverScene({
|
||||||
<span>
|
<span>
|
||||||
{workerAvailable
|
{workerAvailable
|
||||||
? "Запустите PX4/Gazebo — модель останется доступна для осмотра."
|
? "Запустите PX4/Gazebo — модель останется доступна для осмотра."
|
||||||
: "Полигон появится в шапке после подтверждения Simulation Worker."}
|
: "Прогоны и датасеты доступны; запуск и управление откроются после подтверждения Simulation Worker."}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ let stopPolygonWorker;
|
||||||
let PolygonWorkerContractError;
|
let PolygonWorkerContractError;
|
||||||
let workspaceById;
|
let workspaceById;
|
||||||
let workspacesForRoot;
|
let workspacesForRoot;
|
||||||
let rootsForCapabilities;
|
let roots;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
server = await createServer({
|
server = await createServer({
|
||||||
|
|
@ -51,7 +51,7 @@ before(async () => {
|
||||||
({
|
({
|
||||||
workspaceById,
|
workspaceById,
|
||||||
workspacesForRoot,
|
workspacesForRoot,
|
||||||
rootsForCapabilities,
|
roots,
|
||||||
} = await server.ssrLoadModule("/src/productModel.ts"));
|
} = await server.ssrLoadModule("/src/productModel.ts"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -290,7 +290,7 @@ function commandAcceptance(overrides = {}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
test("Polygon is a worker-gated product root and keeps a compatible direct route", () => {
|
test("Polygon stays visible while worker capability gates live actions", () => {
|
||||||
assert.deepEqual(resolvePolygonRunRoute("?workspace=polygon-run"), {
|
assert.deepEqual(resolvePolygonRunRoute("?workspace=polygon-run"), {
|
||||||
active: true,
|
active: true,
|
||||||
runId: null,
|
runId: null,
|
||||||
|
|
@ -320,16 +320,7 @@ test("Polygon is a worker-gated product root and keeps a compatible direct route
|
||||||
workspacesForRoot("system").some(({ id }) => id === "polygon-run"),
|
workspacesForRoot("system").some(({ id }) => id === "polygon-run"),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(roots.some(({ id }) => id === "polygon"), true);
|
||||||
rootsForCapabilities({ polygonWorkerAvailable: false })
|
|
||||||
.some(({ id }) => id === "polygon"),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
rootsForCapabilities({ polygonWorkerAvailable: true })
|
|
||||||
.some(({ id }) => id === "polygon"),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Polygon contracts decode path-free evidence and preserve the safety boundary", () => {
|
test("Polygon contracts decode path-free evidence and preserve the safety boundary", () => {
|
||||||
|
|
|
||||||
|
|
@ -164,8 +164,8 @@ fresh loopback-only namespace, the run reached `completed`, and both provider
|
||||||
groups stopped without residue. S1C commit `b7ccca3` adds a persistent
|
groups stopped without residue. S1C commit `b7ccca3` adds a persistent
|
||||||
unprivileged worker agent in the same isolation boundary, a bounded Unix-socket
|
unprivileged worker agent in the same isolation boundary, a bounded Unix-socket
|
||||||
gateway, an explicit `internal-virtual-only` backend gate and a browser-native
|
gateway, an explicit `internal-virtual-only` backend gate and a browser-native
|
||||||
live panel. UI-2 commit `60e7916` promotes that panel into a worker-gated
|
live panel. UI-2 commit `60e7916` promotes that panel into a top-level product
|
||||||
top-level product root and replaces the diagnostic 2D marker with a
|
root and replaces the diagnostic 2D marker with a
|
||||||
browser-native Three.js Ackermann Rover scene. The exact D-only generation
|
browser-native Three.js Ackermann Rover scene. The exact D-only generation
|
||||||
passed 35 target tests and one UI-driven provider run with diagnostic Gazebo
|
passed 35 target tests and one UI-driven provider run with diagnostic Gazebo
|
||||||
`VehicleState`, eight persisted lifecycle events, terminal `completed` and zero
|
`VehicleState`, eight persisted lifecycle events, terminal `completed` and zero
|
||||||
|
|
|
||||||
|
|
@ -834,15 +834,16 @@ safety policy.
|
||||||
|
|
||||||
## 20. UI gate
|
## 20. UI gate
|
||||||
|
|
||||||
Polygon is implemented as a capability-gated seventh top-level bounded context:
|
Polygon is implemented as a permanent seventh top-level bounded context:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Center | Fleet | Observation | Missions | Data | Polygon* | System
|
Center | Fleet | Observation | Missions | Data | Polygon | System
|
||||||
|
|
||||||
* visible only while Mission Core confirms an admitted available Simulation Worker
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Two UI gates are intentionally distinct.
|
The product surface and execution capability are intentionally distinct.
|
||||||
|
`Полигон` and its dataset catalog remain visible without a worker. Worker
|
||||||
|
capability gates only live lifecycle and command actions, which fail closed and
|
||||||
|
show an explicit offline state.
|
||||||
|
|
||||||
The Polygon navigation is task-oriented:
|
The Polygon navigation is task-oriented:
|
||||||
|
|
||||||
|
|
@ -886,7 +887,7 @@ read-only evidence contract backed by the server-owned run repository and is
|
||||||
composed into UI-2. A shared review deployment requires a reviewed read-only D
|
composed into UI-2. A shared review deployment requires a reviewed read-only D
|
||||||
mount or a co-located backend; SSH is not a product data plane.
|
mount or a co-located backend; SSH is not a product data plane.
|
||||||
|
|
||||||
### UI-2 — worker-gated top-level Polygon
|
### UI-2 — permanent Polygon with worker-gated live actions
|
||||||
|
|
||||||
The full UI-2 control/scenario gate begins only after:
|
The full UI-2 control/scenario gate begins only after:
|
||||||
|
|
||||||
|
|
@ -894,8 +895,10 @@ The full UI-2 control/scenario gate begins only after:
|
||||||
2. S1 target backend lifecycle and command authority are proven;
|
2. S1 target backend lifecycle and command authority are proven;
|
||||||
3. run history is persisted independently of a browser session.
|
3. run history is persisted independently of a browser session.
|
||||||
|
|
||||||
Commit `60e7916` accepts the presentation/lifecycle slice before all three
|
Commit `60e7916` accepted the presentation/lifecycle slice before all three
|
||||||
conditions are complete: conditional header root, direct workspace,
|
conditions were complete. The root is now permanent so datasets and persisted
|
||||||
|
evidence remain reachable while the worker is offline; live start/stop and
|
||||||
|
commands remain capability-gated. The slice includes the direct workspace,
|
||||||
virtual-only start/stop, 3D rover inspection, diagnostic ENU pose and read-only
|
virtual-only start/stop, 3D rover inspection, diagnostic ENU pose and read-only
|
||||||
history. It does not claim command authority. Scenarios, pause/step/reset,
|
history. It does not claim command authority. Scenarios, pause/step/reset,
|
||||||
canonical rover commands, Compare and Report remain planned. UI-2 cannot own
|
canonical rover commands, Compare and Report remain planned. UI-2 cannot own
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,10 @@ reports easy to misuse.
|
||||||
13. S1 uses exact `px4_msgs` directly. The experimental
|
13. S1 uses exact `px4_msgs` directly. The experimental
|
||||||
`px4-ros2-interface-lib` is not a mandatory S1 dependency.
|
`px4-ros2-interface-lib` is not a mandatory S1 dependency.
|
||||||
14. The read-only direct UI-0 run view consumes proven target S1B persisted
|
14. The read-only direct UI-0 run view consumes proven target S1B persisted
|
||||||
history through GET-only server routes. It is hidden from navigation and
|
history through GET-only server routes and cannot mutate a run. The
|
||||||
cannot mutate a run. Top-level Polygon UI and control remain gated on
|
top-level Polygon product surface remains visible so datasets and persisted
|
||||||
accepted S1 target command/lifecycle behavior.
|
evidence are reachable offline; live lifecycle and command actions remain
|
||||||
|
gated on admitted worker capability and accepted S1 target behavior.
|
||||||
15. Real actuator authority requires a new decision and physical safety gate.
|
15. Real actuator authority requires a new decision and physical safety gate.
|
||||||
16. S0 processes run inside an ephemeral loopback-only Linux network namespace.
|
16. S0 processes run inside an ephemeral loopback-only Linux network namespace.
|
||||||
Micro XRCE-DDS Agent's UDP wildcard bind is acceptable only inside that
|
Micro XRCE-DDS Agent's UDP wildcard bind is acceptable only inside that
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue