update model

This commit is contained in:
vee
2026-08-14 11:17:17 +08:00
parent 1a96d39426
commit 6d768aaef4
56 changed files with 424 additions and 148 deletions
+128 -12
View File
@@ -2549,7 +2549,14 @@ function updateSeatTwin(actuatorState) {
}
const REFERENCE_VEHICLE_ROOT = "/assets/models/byd-tang/";
const REFERENCE_VEHICLE_URL = `${REFERENCE_VEHICLE_ROOT}byd-tang-2024.fbx`;
const REFERENCE_VEHICLE_ASSET_VERSION = "77a0fae7";
const REFERENCE_VEHICLE_LOAD_TIMEOUT_MS = 30000;
function referenceVehicleAssetUrl(fileName) {
return `${REFERENCE_VEHICLE_ROOT}${encodeURIComponent(fileName)}?v=${REFERENCE_VEHICLE_ASSET_VERSION}`;
}
const REFERENCE_VEHICLE_URL = referenceVehicleAssetUrl("byd-tang-2024.fbx");
function referenceMaterialDescriptor(material) {
const textureNames = [
@@ -3643,18 +3650,34 @@ function loadReferenceVehicleModel(THREE, scene, vehicleSystems, fallback) {
reject(new Error("FBXLoader is unavailable"));
return;
}
let settled = false;
const settle = (callback, value) => {
if (settled) return false;
settled = true;
window.clearTimeout(timeoutId);
callback(value);
return true;
};
const timeoutId = window.setTimeout(() => {
settle(
reject,
new Error(`Reference vehicle load timed out after ${REFERENCE_VEHICLE_LOAD_TIMEOUT_MS} ms`),
);
}, REFERENCE_VEHICLE_LOAD_TIMEOUT_MS);
const manager = new THREE.LoadingManager();
manager.setURLModifier((url) => {
const cleanUrl = String(url).replaceAll("\\", "/");
const cleanUrl = String(url).replaceAll("\\", "/").split(/[?#]/, 1)[0];
const fileName = decodeURIComponent(cleanUrl.split("/").pop() || "");
if (!/\.(png|jpe?g)$/i.test(fileName)) return url;
return `${REFERENCE_VEHICLE_ROOT}${encodeURIComponent(fileName)}`;
return referenceVehicleAssetUrl(fileName);
});
const loader = new FBXLoaderClass(manager);
loader.setResourcePath(REFERENCE_VEHICLE_ROOT);
loader.load(
REFERENCE_VEHICLE_URL,
(vehicle) => {
if (settled) return;
try {
const normalized = normalizeReferenceVehicle(THREE, vehicle);
const driverCushion = measureReferenceDriverCushion(THREE, vehicle);
const passengerCushion = measureReferencePassengerCushion(THREE, vehicle);
@@ -3709,7 +3732,10 @@ function loadReferenceVehicleModel(THREE, scene, vehicleSystems, fallback) {
driverCushion,
passengerCushion,
}));
resolve({ vehicle, report, normalized, driverCushion, passengerCushion });
settle(resolve, { vehicle, report, normalized, driverCushion, passengerCushion });
} catch (error) {
settle(reject, error);
}
},
(event) => {
if (!fallback || !event.total) return;
@@ -3717,7 +3743,7 @@ function loadReferenceVehicleModel(THREE, scene, vehicleSystems, fallback) {
const label = fallback.querySelector("span");
if (label) label.textContent = `正在加载整车参考模型 · ${progress}%`;
},
reject,
(error) => settle(reject, error),
);
});
}
@@ -3726,7 +3752,33 @@ function initSeatTwin() {
const canvas = $("#seatTwinCanvas");
const stage = $("#seatTwin");
const fallback = $("#twinFallback");
if (!canvas || !stage) return;
if (!canvas || !stage || state.seatTwin) return;
const stageHasLayout = () => {
const bounds = stage.getBoundingClientRect();
return stage.isConnected && bounds.width >= 2 && bounds.height >= 2;
};
if (!stageHasLayout()) {
if (stage.dataset.twinInitPending === "true") return;
stage.dataset.twinInitPending = "true";
let pendingObserver = null;
const retryInitialization = () => {
if (!stageHasLayout()) return;
pendingObserver?.disconnect();
window.removeEventListener("resize", scheduleRetry);
window.removeEventListener("cognitive-agent:module-change", scheduleRetry);
delete stage.dataset.twinInitPending;
initSeatTwin();
};
const scheduleRetry = () => window.requestAnimationFrame(retryInitialization);
if (window.ResizeObserver) {
pendingObserver = new ResizeObserver(retryInitialization);
pendingObserver.observe(stage);
}
window.addEventListener("resize", scheduleRetry);
window.addEventListener("cognitive-agent:module-change", scheduleRetry);
scheduleRetry();
return;
}
if (!window.THREE) {
fallback.classList.add("error");
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>3D 引擎加载失败,精确状态仍可用</span>';
@@ -3735,7 +3787,16 @@ function initSeatTwin() {
}
const THREE = window.THREE;
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: "high-performance" });
let renderer;
try {
renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: "high-performance" });
} catch (error) {
console.error("[vehicle-twin] failed to initialize WebGL", error);
fallback.classList.add("error");
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>3D 渲染器初始化失败,状态数据仍可用</span>';
refreshIcons();
return;
}
renderer.autoClear = false;
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
if ("outputColorSpace" in renderer) renderer.outputColorSpace = THREE.SRGBColorSpace;
@@ -3883,6 +3944,7 @@ function initSeatTwin() {
applyTwinSceneTheme(state.seatTwin);
});
canvas.dataset.referenceModelState = "loading";
loadReferenceVehicleModel(THREE, scene, vehicleSystems, fallback)
.then(({ vehicle, report, normalized, driverCushion, passengerCushion }) => {
if (!state.seatTwin) return;
@@ -3920,11 +3982,12 @@ function initSeatTwin() {
.toArray()
.map((value) => value.toFixed(2))
.join("x");
canvas.dataset.referenceModelLoaded = "true";
canvas.dataset.referenceModelState = "loaded";
fallback.hidden = true;
})
.catch((error) => {
console.error("[vehicle-twin] failed to load reference vehicle model", error);
canvas.dataset.referenceModelState = "failed";
fallback.classList.add("error");
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>实车模型加载失败,已切换轻量回退模型</span>';
refreshIcons();
@@ -3940,17 +4003,64 @@ function initSeatTwin() {
});
}
let stageWasVisible = false;
function resize() {
const width = Math.max(1, canvas.clientWidth);
const height = Math.max(1, canvas.clientHeight);
const width = Math.max(1, Math.round(stage.clientWidth));
const height = Math.max(1, Math.round(stage.clientHeight));
renderer.setSize(width, height, false);
camera.aspect = width / height;
camera.updateProjectionMatrix();
state.seatTwin.narrowViewport = width < 600;
}
new ResizeObserver(resize).observe(stage);
function synchronizeTwinViewport({ reset = false } = {}) {
if (!stageHasLayout()) {
stageWasVisible = false;
return false;
}
resize();
if (reset || !stageWasVisible) resetView();
stageWasVisible = true;
return true;
}
const scheduleViewportSync = () => {
window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
synchronizeTwinViewport();
}));
};
if (window.ResizeObserver) {
const stageObserver = new ResizeObserver(() => synchronizeTwinViewport());
stageObserver.observe(stage);
state.seatTwin.resizeObserver = stageObserver;
}
window.addEventListener("resize", scheduleViewportSync);
window.addEventListener("cognitive-agent:module-change", scheduleViewportSync);
canvas.addEventListener("webglcontextlost", (event) => {
event.preventDefault();
canvas.dataset.contextLost = "true";
fallback.hidden = false;
fallback.classList.add("error");
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>3D 上下文已中断,正在等待恢复</span>';
refreshIcons();
});
canvas.addEventListener("webglcontextrestored", () => {
delete canvas.dataset.contextLost;
synchronizeTwinViewport({ reset: true });
const modelState = canvas.dataset.referenceModelState;
fallback.hidden = modelState === "loaded";
if (modelState === "loading") {
fallback.classList.remove("error");
fallback.innerHTML = '<i data-lucide="loader-circle"></i><span>正在加载整车参考模型</span>';
refreshIcons();
} else if (modelState === "failed") {
fallback.classList.add("error");
fallback.innerHTML = '<i data-lucide="triangle-alert"></i><span>实车模型加载失败,已切换轻量回退模型</span>';
refreshIcons();
window.setTimeout(() => { fallback.hidden = true; }, 3200);
}
});
synchronizeTwinViewport({ reset: true });
stage.addEventListener("pointerdown", (event) => {
state.seatTwin.dragging = true;
state.seatTwin.pointer = { x: event.clientX, y: event.clientY };
@@ -4052,6 +4162,12 @@ function initSeatTwin() {
function animate(timestamp = 0) {
const twin = state.seatTwin;
if (!twin) return;
if (!stageHasLayout() || canvas.dataset.contextLost === "true") {
stageWasVisible = false;
window.requestAnimationFrame(animate);
return;
}
if (!stageWasVisible) synchronizeTwinViewport({ reset: true });
Object.values(twin.models).forEach((model) => {
Object.keys(model.positions).forEach((axis) => {
const delta = model.targets[axis] - model.positions[axis];
@@ -6556,7 +6672,7 @@ document.addEventListener("DOMContentLoaded", async () => {
initJointTwinLayout();
initResultWorkspace();
refreshIcons();
initSeatTwin();
window.requestAnimationFrame(initSeatTwin);
$("#requestForm").addEventListener("submit", submitRequest);
$("#utterance").addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
+2 -2
View File
@@ -19,14 +19,14 @@
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
<script src="/network.js?v=android-pad-1" defer></script>
<script src="/app.js?v=dual-seat-80" defer></script>
<script src="/app.js?v=dual-seat-81" defer></script>
<script type="module" src="/gesture-control.js?v=android-pad-7"></script>
<script src="/pwa.js?v=android-pad-1" defer></script>
<script src="/console-v2.js?v=desktop-refactor-35" defer></script>
<script src="/self-extension.js?v=extension-20" defer></script>
<script src="/self-calibration.js?v=calibration-6" defer></script>
<script src="/self-repair.js?v=repair-20" defer></script>
<script src="/self-repair-seat3d.js?v=repair-18" defer></script>
<script src="/self-repair-seat3d.js?v=repair-19" defer></script>
</head>
<body>
<header class="topbar">
+71 -4
View File
@@ -1,6 +1,33 @@
(function () {
"use strict";
function viewerHasLayout(viewer) {
const bounds = viewer.getBoundingClientRect();
return viewer.isConnected && bounds.width >= 2 && bounds.height >= 2;
}
function deferSeat3dUntilVisible(viewer, options) {
if (viewer.dataset.seat3dPending === "true") return;
viewer.dataset.seat3dPending = "true";
let observer = null;
const retry = () => {
if (!viewerHasLayout(viewer)) return;
observer?.disconnect();
window.removeEventListener("resize", scheduleRetry);
window.removeEventListener("cognitive-agent:module-change", scheduleRetry);
delete viewer.dataset.seat3dPending;
initSeat3d(options);
};
const scheduleRetry = () => window.requestAnimationFrame(retry);
if (window.ResizeObserver) {
observer = new ResizeObserver(retry);
observer.observe(viewer);
}
window.addEventListener("resize", scheduleRetry);
window.addEventListener("cognitive-agent:module-change", scheduleRetry);
scheduleRetry();
}
function initSeat3d(options) {
const {
canvasId,
@@ -15,6 +42,10 @@
const canvas = document.getElementById(canvasId);
const viewer = document.getElementById(viewerId);
if (!canvas || !viewer || viewer.dataset.seat3dInitialized === "true") return;
if (!viewerHasLayout(viewer)) {
deferSeat3dUntilVisible(viewer, options);
return;
}
viewer.dataset.seat3dInitialized = "true";
const markReady = (fallback = false) => {
@@ -387,6 +418,24 @@
camera.updateProjectionMatrix();
}
let viewerWasVisible = false;
function synchronizeViewport({ reset = false } = {}) {
if (!viewerHasLayout(viewer)) {
viewerWasVisible = false;
return false;
}
resize();
if (reset || !viewerWasVisible) resetOrbit();
viewerWasVisible = true;
return true;
}
const scheduleViewportSync = () => {
window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
synchronizeViewport();
}));
};
function onHealingState(event) {
const detail = event.detail || {};
(detail.axes || []).forEach((axis) => {
@@ -404,6 +453,11 @@
function animate() {
window.requestAnimationFrame(animate);
if (!viewerHasLayout(viewer) || canvas.dataset.contextLost === "true") {
viewerWasVisible = false;
return;
}
if (!viewerWasVisible) synchronizeViewport({ reset: true });
for (let index = 0; index < current.axes.length; index += 1) {
current.axes[index] += (desired.axes[index] - current.axes[index]) * 0.055;
}
@@ -486,10 +540,23 @@
window.addEventListener(stateEvent, onHealingState);
window.addEventListener(resetEvent, resetOrbit);
if (window.ResizeObserver) new ResizeObserver(resize).observe(viewer);
window.addEventListener("resize", resize);
resize();
resetOrbit();
if (window.ResizeObserver) {
const resizeObserver = new ResizeObserver(() => synchronizeViewport());
resizeObserver.observe(viewer);
}
window.addEventListener("resize", scheduleViewportSync);
window.addEventListener("cognitive-agent:module-change", scheduleViewportSync);
canvas.addEventListener("webglcontextlost", (event) => {
event.preventDefault();
canvas.dataset.contextLost = "true";
viewer.classList.add("context-lost");
});
canvas.addEventListener("webglcontextrestored", () => {
delete canvas.dataset.contextLost;
viewer.classList.remove("context-lost");
synchronizeViewport({ reset: true });
});
synchronizeViewport({ reset: true });
animate();
window.requestAnimationFrame(() => markReady());
+4 -4
View File
@@ -1,4 +1,4 @@
const CACHE_VERSION = "cognitive-agent-pad-v116";
const CACHE_VERSION = "cognitive-agent-pad-v117";
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
const APP_SHELL = [
@@ -6,7 +6,7 @@ const APP_SHELL = [
"/index.html",
"/styles.css?v=dual-seat-9",
"/network.js?v=android-pad-1",
"/app.js?v=dual-seat-80",
"/app.js?v=dual-seat-81",
"/gesture-control.js?v=android-pad-7",
"/pwa.js?v=android-pad-1",
"/console-v2.css?v=desktop-refactor-106",
@@ -17,8 +17,8 @@ const APP_SHELL = [
"/self-calibration.js?v=calibration-6",
"/self-repair.css?v=repair-21",
"/self-repair.js?v=repair-20",
"/self-repair-seat3d.js?v=repair-18",
"/assets/models/n7_seat.glb",
"/self-repair-seat3d.js?v=repair-19",
"/assets/models/n7_seat.glb?v=e7f50f03",
"/manifest.webmanifest",
"/icons/app-icon-192.png",
"/icons/app-icon-512.png",
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
9136
@@ -1,61 +0,0 @@
id: k-c78dff
name: K歌模式
aliases:
- K歌模式
- K歌
description: 用户固化的长期模式配置。
icon: bookmark
scope:
vehicle_ids:
- denza_n7
operations:
- domain: ambient_light
capability: ambient_light.set_power
target_scope: vehicle
operation: activate
parameters:
state: 'on'
reason: 开启氛围灯以营造K歌场景。
- domain: ambient_light
capability: ambient_light.set_illumination
target_scope: vehicle
operation: set
parameters:
color_hex: '#B84DFF'
brightness_pct: 65
reason: 使用明快紫色和中高亮度营造活跃舞台氛围。
- domain: climate
capability: climate.set_power
target_scope: vehicle
operation: activate
parameters:
state: 'on'
reason: 空调当前关闭,需先开启以维持K歌时的座舱舒适度。
- domain: climate
capability: climate.set_temperature
target_scope: driver
operation: set
parameters:
temperature_c: 24
reason: 采用舒适且保守的座舱温度。
- domain: climate
capability: climate.set_fan_level
target_scope: vehicle
operation: set
parameters:
level: 2
reason: 采用低风量,兼顾通风并减少对歌声的干扰。
- domain: climate
capability: climate.set_ac
target_scope: vehicle
operation: activate
parameters:
state: 'on'
reason: 启用制冷除湿以维持多人演唱时的舒适度。
- domain: climate
capability: climate.set_cycle_mode
target_scope: vehicle
operation: set
parameters:
mode: internal
reason: 采用内循环以稳定座舱温度并减少外部噪声和气味。
@@ -0,0 +1,46 @@
id: mode-ceff921663
name: 躺平模式
aliases:
- 躺平模式
- 躺平
description: 用户固化的长期模式配置。
icon: bookmark
scope:
vehicle_ids:
- denza_n7
operations:
- domain: seat
capability: seat.set_longitudinal_position
target_scope: passenger
operation: set
parameters:
position_pct: 90
reason: 用户固化的座椅位置模式动作。
- domain: seat
capability: seat.set_cushion_tilt
target_scope: passenger
operation: set
parameters:
position_pct: 40
reason: 用户固化的座椅位置模式动作。
- domain: seat
capability: seat.set_backrest_recline
target_scope: passenger
operation: set
parameters:
position_pct: 80
reason: 用户固化的座椅位置模式动作。
- domain: seat
capability: seat.set_legrest_height
target_scope: passenger
operation: set
parameters:
position_pct: 75
reason: 用户固化的座椅位置模式动作。
- domain: seat
capability: seat.set_legrest_extension
target_scope: passenger
operation: set
parameters:
position_pct: 80
reason: 用户固化的座椅位置模式动作。
+16 -5
View File
@@ -243,7 +243,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
if path.startswith("/api/requests/"):
self._serve_request(path.removeprefix("/api/requests/"))
return
self._serve_static(path)
self._serve_static(path, parsed_url.query)
def do_POST(self) -> None: # noqa: N802 - stdlib handler API
path = unquote(urlparse(self.path).path)
@@ -942,17 +942,24 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
return
self._json(json.loads(result_path.read_text(encoding="utf-8")))
def _serve_static(self, path: str) -> None:
def _serve_static(self, path: str, query: str = "") -> None:
relative_path = "index.html" if path in {"", "/"} else path.lstrip("/")
self._serve_static_file(relative_path)
versioned = bool(parse_qs(query).get("v"))
self._serve_static_file(relative_path, versioned=versioned)
def _serve_static_file(self, relative_path: str) -> None:
def _serve_static_file(self, relative_path: str, *, versioned: bool = False) -> None:
candidate = (STATIC_ROOT / relative_path).resolve()
try:
candidate.relative_to(STATIC_ROOT.resolve())
except ValueError:
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
if not candidate.is_file() and (
Path(relative_path).suffix
or relative_path.startswith(("assets/", "vendor/", "icons/"))
):
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
if not candidate.is_file():
candidate = STATIC_ROOT / "index.html"
content_type, _ = mimetypes.guess_type(candidate.name)
@@ -972,8 +979,12 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
)
self.send_header("Content-Length", str(len(content)))
cache_control = "no-cache"
if relative_path.startswith(("vendor/", "assets/", "icons/")):
if relative_path.startswith(("vendor/", "icons/")) or (
relative_path.startswith("assets/") and versioned
):
cache_control = "public, max-age=31536000, immutable"
elif relative_path.startswith("assets/"):
cache_control = "no-cache"
elif candidate.name in {"index.html", "login.html", "service-worker.js"}:
cache_control = "no-cache"
self.send_header("Cache-Control", cache_control)
+24 -3
View File
@@ -177,7 +177,7 @@ operations:
def test_static_console_and_runtime_endpoint(self) -> None:
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
html = response.read().decode("utf-8")
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-80", timeout=3) as response:
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-81", timeout=3) as response:
app_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/gesture-control.js?v=android-pad-7", timeout=3
@@ -199,6 +199,10 @@ operations:
f"{self.base_url}/self-extension.css?v=extension-18", timeout=3
) as response:
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
with self.opener.open(
f"{self.base_url}/service-worker.js", timeout=3
) as response:
service_worker = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/api/extension/health", timeout=3
) as response:
@@ -219,7 +223,7 @@ operations:
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
self.assertNotIn("透明车体 · 实体设备", html)
self.assertIn('src="/app.js?v=dual-seat-80"', html)
self.assertIn('src="/app.js?v=dual-seat-81"', html)
self.assertIn('href="/styles.css?v=dual-seat-9"', html)
self.assertIn('src="/gesture-control.js?v=android-pad-7"', html)
self.assertNotIn('class="vehicle-system-hud"', html)
@@ -438,6 +442,23 @@ operations:
self.assertIn("function createReferenceFrontLampHighlights", app_script)
self.assertIn("referenceFrontLampMaterials", app_script)
self.assertIn("targets.windowPositions", app_script)
self.assertIn(
'const REFERENCE_VEHICLE_ASSET_VERSION = "77a0fae7"', app_script
)
self.assertIn(
"const REFERENCE_VEHICLE_LOAD_TIMEOUT_MS = 30000", app_script
)
self.assertIn(
'referenceVehicleAssetUrl("byd-tang-2024.fbx")', app_script
)
self.assertIn("const stageHasLayout = () =>", app_script)
self.assertIn("window.requestAnimationFrame(initSeatTwin)", app_script)
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
self.assertIn('"cognitive-agent:module-change"', app_script)
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v117"', service_worker)
self.assertIn(
'"/assets/models/n7_seat.glb?v=e7f50f03"', service_worker
)
self.assertIn("hardware_access_required", app_script)
self.assertIn('button.setAttribute("aria-label", "关闭摄像头")', gesture_script)
self.assertIn("function refreshCameraAvailability", gesture_script)
@@ -647,7 +668,7 @@ operations:
) as response:
extension_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/app.js?v=dual-seat-80", timeout=3
f"{self.base_url}/app.js?v=dual-seat-81", timeout=3
) as response:
app_script = response.read().decode("utf-8")
+16
View File
@@ -147,6 +147,18 @@ class TestSelfCalibrationApi:
model = response.read()
content_type = response.headers["Content-Type"]
content_security_policy = response.headers["Content-Security-Policy"]
unversioned_cache_control = response.headers["Cache-Control"]
with urllib.request.urlopen(
f"{self.base_url}/assets/models/n7_seat.glb?v=e7f50f03",
timeout=3,
) as response:
versioned_model_prefix = response.read(4)
versioned_cache_control = response.headers["Cache-Control"]
with pytest.raises(urllib.error.HTTPError) as missing_model:
urllib.request.urlopen(
f"{self.base_url}/assets/models/missing-seat.glb?v=missing",
timeout=3,
)
assert 'href="/self-calibration.css?v=calibration-10"' in html
assert 'src="/self-calibration.js?v=calibration-6"' in html
@@ -173,6 +185,10 @@ class TestSelfCalibrationApi:
assert '["self-calibration", "self-repair"].includes(module)' in console_script
assert 'setRole("admin", { allowModuleTransition: true })' in console_script
assert len(model) == 845_992
assert versioned_model_prefix == model[:4]
assert content_type.startswith("model/gltf-binary")
assert unversioned_cache_control == "no-cache"
assert versioned_cache_control == "public, max-age=31536000, immutable"
assert missing_model.value.code == 404
assert "img-src 'self' data: blob:;" in content_security_policy
assert "connect-src 'self' blob:;" in content_security_policy
+7 -2
View File
@@ -154,7 +154,7 @@ class TestSelfRepairApi:
) as response:
styles = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/self-repair-seat3d.js?v=repair-18", timeout=3
f"{self.base_url}/self-repair-seat3d.js?v=repair-19", timeout=3
) as response:
seat3d = response.read().decode("utf-8")
with urllib.request.urlopen(
@@ -168,7 +168,7 @@ class TestSelfRepairApi:
assert 'SELF-REPAIR' in script
assert 'padding: var(--v2-module-title-block) 0 0' in styles
assert 'margin-inline: var(--v2-module-title-inline)' in styles
assert 'src="/self-repair-seat3d.js?v=repair-18"' in html
assert 'src="/self-repair-seat3d.js?v=repair-19"' in html
assert "量产故障注入" in script
assert "运行与自修复日志" in script
assert "启动座椅运行" in script
@@ -203,6 +203,11 @@ class TestSelfRepairApi:
assert 'viewer.addEventListener("pointerdown"' in seat3d
assert 'window.addEventListener("pointermove"' in seat3d
assert "window.addEventListener(resetEvent" in seat3d
assert "function viewerHasLayout(viewer)" in seat3d
assert "function deferSeat3dUntilVisible(viewer, options)" in seat3d
assert "if (!viewerHasLayout(viewer))" in seat3d
assert 'window.addEventListener("cognitive-agent:module-change"' in seat3d
assert 'canvas.addEventListener("webglcontextlost"' in seat3d
assert '["self-calibration", "self-repair"].includes(module)' in console_script
assert 'roleSelect.disabled = ui.activeModule === "self-repair"' in console_script
@@ -0,0 +1 @@
{"heartbeat_epoch": 1786612896.954201, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
@@ -0,0 +1,3 @@
BRIDGE_VERSION = 'runtime-test-v1'
def run_bridge(transport=None, stop_event=None):
stop_event.wait(2.0)
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786612902.0072305, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786612902.0072305, "0x302": 1786612902.0072305, "0x303": 1786612902.0072305, "0x304": 1786612902.0072305, "0x305": 1786612902.0072305, "0x306": 1786612902.0072305, "0x307": 1786612902.0072305}}
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786612902.1554296, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786612902.1554296, "0x302": 1786612902.1554296, "0x303": 1786612902.1554296, "0x304": 1786612902.1554296, "0x305": 1786612902.1554296, "0x306": 1786612902.1554296, "0x307": 1786612902.1554296}}
@@ -0,0 +1 @@
{"heartbeat_epoch": 1786614297.8563914, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
@@ -0,0 +1,3 @@
BRIDGE_VERSION = 'runtime-test-v1'
def run_bridge(transport=None, stop_event=None):
stop_event.wait(2.0)
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786614302.9093528, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786614302.9093528, "0x302": 1786614302.9093528, "0x303": 1786614302.9093528, "0x304": 1786614302.9093528, "0x305": 1786614302.9093528, "0x306": 1786614302.9093528, "0x307": 1786614302.9093528}}
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786614303.057585, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786614303.057585, "0x302": 1786614303.057585, "0x303": 1786614303.057585, "0x304": 1786614303.057585, "0x305": 1786614303.057585, "0x306": 1786614303.057585, "0x307": 1786614303.057585}}
@@ -0,0 +1 @@
{"heartbeat_epoch": 1786614211.5981486, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
@@ -0,0 +1,3 @@
BRIDGE_VERSION = 'runtime-test-v1'
def run_bridge(transport=None, stop_event=None):
stop_event.wait(2.0)
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786614216.6407876, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786614216.6407876, "0x302": 1786614216.6407876, "0x303": 1786614216.6407876, "0x304": 1786614216.6407876, "0x305": 1786614216.6407876, "0x306": 1786614216.6407876, "0x307": 1786614216.6407876}}
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786614216.7809346, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786614216.7809346, "0x302": 1786614216.7809346, "0x303": 1786614216.7809346, "0x304": 1786614216.7809346, "0x305": 1786614216.7809346, "0x306": 1786614216.7809346, "0x307": 1786614216.7809346}}
@@ -0,0 +1 @@
{"heartbeat_epoch": 1786628979.608127, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
@@ -0,0 +1,3 @@
BRIDGE_VERSION = 'runtime-test-v1'
def run_bridge(transport=None, stop_event=None):
stop_event.wait(2.0)
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786628984.6589825, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786628984.6589825, "0x302": 1786628984.6589825, "0x303": 1786628984.6589825, "0x304": 1786628984.6589825, "0x305": 1786628984.6589825, "0x306": 1786628984.6589825, "0x307": 1786628984.6589825}}
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786628984.8052018, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786628984.8052018, "0x302": 1786628984.8052018, "0x303": 1786628984.8052018, "0x304": 1786628984.8052018, "0x305": 1786628984.8052018, "0x306": 1786628984.8052018, "0x307": 1786628984.8052018}}
@@ -0,0 +1 @@
{"heartbeat_epoch": 1786608369.3573637, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
@@ -0,0 +1,3 @@
BRIDGE_VERSION = 'runtime-test-v1'
def run_bridge(transport=None, stop_event=None):
stop_event.wait(2.0)
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786608374.4077742, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786608374.4077742, "0x302": 1786608374.4077742, "0x303": 1786608374.4077742, "0x304": 1786608374.4077742, "0x305": 1786608374.4077742, "0x306": 1786608374.4077742, "0x307": 1786608374.4077742}}
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786608374.5544136, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786608374.5544136, "0x302": 1786608374.5544136, "0x303": 1786608374.5544136, "0x304": 1786608374.5544136, "0x305": 1786608374.5544136, "0x306": 1786608374.5544136, "0x307": 1786608374.5544136}}
@@ -0,0 +1 @@
{"heartbeat_epoch": 1786607635.6544752, "bridge_state": "RUNNING", "current_request_id": "SELF_HEAL_BENCH_1"}
@@ -0,0 +1,3 @@
BRIDGE_VERSION = 'runtime-test-v1'
def run_bridge(transport=None, stop_event=None):
stop_event.wait(2.0)
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786607640.7259998, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 15, "qualified": false}, "frame_epochs": {"0x301": 1786607640.7259998, "0x302": 1786607640.7259998, "0x303": 1786607640.7259998, "0x304": 1786607640.7259998, "0x305": 1786607640.7259998, "0x306": 1786607640.7259998, "0x307": 1786607640.7259998}}
@@ -0,0 +1 @@
{"bridge_version": "runtime-test-v1", "heartbeat_epoch": 1786607640.8686898, "current_request_id": null, "system": {"firmware_revision": 15}, "firmware_identity": {"firmware_build_id": 16, "qualified": true}, "frame_epochs": {"0x301": 1786607640.8686898, "0x302": 1786607640.8686898, "0x303": 1786607640.8686898, "0x304": 1786607640.8686898, "0x305": 1786607640.8686898, "0x306": 1786607640.8686898, "0x307": 1786607640.8686898}}