fix: clarify EM overview path layers
This commit is contained in:
@@ -6,6 +6,10 @@
|
|||||||
--previous-trajectory: #8d959d;
|
--previous-trajectory: #8d959d;
|
||||||
--handoff: #d87918;
|
--handoff: #d87918;
|
||||||
--failure: #b42318;
|
--failure: #b42318;
|
||||||
|
--world-coarse-path: #7e8790;
|
||||||
|
--world-smoothed-path: #168b83;
|
||||||
|
--world-active-segment: #3f78a8;
|
||||||
|
--world-em-trajectory: #df6b1f;
|
||||||
}
|
}
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
body { margin: 0; background: var(--paper); color: var(--text); font: 14px/1.45 "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; }
|
body { margin: 0; background: var(--paper); color: var(--text); font: 14px/1.45 "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; }
|
||||||
@@ -43,22 +47,25 @@ main > section[hidden] { display: none !important; }
|
|||||||
.world-axis-zero { stroke: #7c8791; stroke-opacity: .72; stroke-width: 1.1; vector-effect: non-scaling-stroke; }
|
.world-axis-zero { stroke: #7c8791; stroke-opacity: .72; stroke-width: 1.1; vector-effect: non-scaling-stroke; }
|
||||||
.world-axis-tick { fill: #68737d; font-family: ui-monospace, Consolas, monospace; }
|
.world-axis-tick { fill: #68737d; font-family: ui-monospace, Consolas, monospace; }
|
||||||
.world-axis-label { fill: #48535d; font-family: "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; font-weight: 600; }
|
.world-axis-label { fill: #48535d; font-family: "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; font-weight: 600; }
|
||||||
.world-static { stroke: #8d959d; stroke-width: .9; }
|
.world-static { stroke: #8d959d; stroke-opacity: .38; stroke-width: 1.4; }
|
||||||
.world-future { stroke: #b6bec6; stroke-dasharray: 5 4; stroke-width: .85; }
|
.world-future { stroke: #b6bec6; stroke-dasharray: 5 4; stroke-width: 1.2; }
|
||||||
.world-active { stroke: #1769aa; stroke-width: 1.1; }
|
.world-active { stroke: var(--world-active-segment); stroke-width: 1.5; }
|
||||||
.world-previous { stroke: #8d959d; stroke-dasharray: 5 4; stroke-width: .85; }
|
.world-previous { stroke: #887493; stroke-opacity: .66; stroke-dasharray: 7 5; stroke-width: 2; }
|
||||||
.world-coarse { stroke: #9aa1a8; stroke-opacity: .48; stroke-dasharray: 5 4; stroke-width: .85; }
|
.world-coarse { stroke: var(--world-coarse-path); stroke-opacity: .40; stroke-dasharray: 7 5; stroke-width: 2; }
|
||||||
.world-local-g2 { stroke: #4f5963; stroke-width: .95; }
|
.world-local-g2 { stroke: var(--world-smoothed-path); stroke-opacity: .65; stroke-width: 4; }
|
||||||
.world-segment-active { stroke: #5d91bd; stroke-opacity: .62; stroke-width: 1.0; }
|
.world-segment-active { stroke: var(--world-active-segment); stroke-opacity: .50; stroke-width: 1.5; }
|
||||||
.world-direction-inactive { stroke: #c4cad0; stroke-opacity: .52; stroke-dasharray: 4 4; stroke-width: .8; }
|
.world-direction-inactive { stroke: #aab2b9; stroke-opacity: .30; stroke-dasharray: 5 5; stroke-width: 1.1; }
|
||||||
.world-current { stroke: #1769aa; stroke-width: 1.15; }
|
.world-current { stroke: var(--world-em-trajectory); stroke-opacity: .96; stroke-width: 2.4; }
|
||||||
.world-marker { fill: #d87918; stroke: #ffffff; stroke-width: .8; }
|
.world-marker-shape { vector-effect: non-scaling-stroke; }
|
||||||
.marker-vehicle .vehicle-outline { fill: none; stroke: #20252b; stroke-width: .7; }
|
.world-marker-label { fill: #20252b; font: 11px/1.2 "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; paint-order: stroke; stroke: rgba(255, 255, 255, .88); stroke-width: 3px; stroke-linejoin: round; }
|
||||||
.marker-vehicle .vehicle-heading { stroke: #20252b; stroke-width: .8; }
|
.world-marker { fill: #d87918; stroke: #ffffff; stroke-width: 1.4; }
|
||||||
.marker-plan-start { fill: #3f7d46; stroke: #ffffff; stroke-width: .6; }
|
.marker-vehicle .vehicle-outline { fill: rgba(32, 37, 43, .16); stroke: #20252b; stroke-width: 1.5; }
|
||||||
.marker-gear-switch { fill: #d87918; stroke: #ffffff; stroke-width: .7; }
|
.marker-vehicle .vehicle-heading { stroke: #20252b; stroke-width: 1.5; stroke-linecap: round; vector-effect: non-scaling-stroke; }
|
||||||
.marker-final-goal { fill: #1769aa; stroke: #ffffff; stroke-width: .7; }
|
.marker-smooth-start { fill: #2f8f56; stroke: #ffffff; stroke-width: 1.5; }
|
||||||
.world-empty-state { fill: #66707a; font-size: .42; }
|
.marker-plan-start { fill: #ffffff; stroke: #2f8f56; stroke-width: 2; }
|
||||||
|
.marker-gear-switch { fill: #d87918; stroke: #ffffff; stroke-width: 1.5; }
|
||||||
|
.marker-final-goal { fill: #1769aa; stroke: #ffffff; stroke-width: 1.5; }
|
||||||
|
.world-empty-state { fill: #66707a; font: 14px/1.2 "Microsoft YaHei", "Noto Sans CJK SC", sans-serif; }
|
||||||
.history-panel, .configuration-panel { margin-bottom: 26px; } #cycle-history { overflow-x: auto; } table { width: 100%; border-collapse: collapse; font-size: 12px; } th, td { padding: 6px 8px; border-bottom: 1px solid var(--grid); text-align: left; white-space: nowrap; } th { color: #59636d; font-weight: 600; }
|
.history-panel, .configuration-panel { margin-bottom: 26px; } #cycle-history { overflow-x: auto; } table { width: 100%; border-collapse: collapse; font-size: 12px; } th, td { padding: 6px 8px; border-bottom: 1px solid var(--grid); text-align: left; white-space: nowrap; } th { color: #59636d; font-weight: 600; }
|
||||||
.config-group { margin: 13px 0; border-top: 1px solid var(--grid); } .config-group h3 { margin: 8px 0; font-size: 13px; } .config-entry { display: grid; grid-template-columns: minmax(110px, 1fr) minmax(130px, 1fr) minmax(90px, 1fr) 70px; gap: 8px; padding: 4px 0; border-bottom: 1px dotted #e5e8eb; } .raw { color: #66707a; font-family: ui-monospace, Consolas, monospace; }
|
.config-group { margin: 13px 0; border-top: 1px solid var(--grid); } .config-group h3 { margin: 8px 0; font-size: 13px; } .config-entry { display: grid; grid-template-columns: minmax(110px, 1fr) minmax(130px, 1fr) minmax(90px, 1fr) 70px; gap: 8px; padding: 4px 0; border-bottom: 1px dotted #e5e8eb; } .raw { color: #66707a; font-family: ui-monospace, Consolas, monospace; }
|
||||||
.failure { color: var(--failure); } .notice { color: var(--handoff); }
|
.failure { color: var(--failure); } .notice { color: var(--handoff); }
|
||||||
|
|||||||
@@ -102,46 +102,86 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function worldPath(points) { return safeArray(points).filter(point => finite(point.x) && finite(point.y)).map((point, index) => (index ? "L" : "M") + point.x + " " + (-point.y)).join(" "); }
|
function worldPath(points) { return safeArray(points).filter(point => finite(point.x) && finite(point.y)).map((point, index) => (index ? "L" : "M") + point.x + " " + (-point.y)).join(" "); }
|
||||||
|
function lineKind(line) { return String(line && line.kind || "").toLowerCase(); }
|
||||||
|
|
||||||
|
function markerTransform(position, unitsPerPixel, headingRadians = 0) {
|
||||||
|
const headingDegrees = (finite(headingRadians) ? headingRadians : 0) * 180 / Math.PI;
|
||||||
|
return `translate(${position.x} ${-position.y}) rotate(${-headingDegrees}) scale(${unitsPerPixel})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendWorldMarker(marker, frame, metrics) {
|
||||||
|
if (!marker.position || !finite(marker.position.x) || !finite(marker.position.y)) return;
|
||||||
|
const kind = String(marker.kind || "").toLowerCase();
|
||||||
|
const pose = frame.vehiclePose;
|
||||||
|
const heading = kind === "vehicle" && pose && finite(pose.headingRadians) ? pose.headingRadians : 0;
|
||||||
|
const group = svg("g", {
|
||||||
|
class: `world-marker-group marker-${kind || "generic"}`,
|
||||||
|
transform: markerTransform(marker.position, metrics.unitsPerPixel, heading)
|
||||||
|
});
|
||||||
|
if (kind === "vehicle") {
|
||||||
|
group.append(svg("path", { d: "M -9 -5 L 5 -5 L 9 0 L 5 5 L -9 5 Z", class: "world-marker-shape vehicle-outline" }));
|
||||||
|
group.append(svg("line", { x1: -4, y1: 0, x2: 7, y2: 0, class: "vehicle-heading" }));
|
||||||
|
} else if (kind === "smooth-start") {
|
||||||
|
group.append(svg("circle", { cx: 0, cy: 0, r: 5, class: "world-marker-shape marker-smooth-start" }));
|
||||||
|
} else if (kind === "plan-start") {
|
||||||
|
group.append(svg("circle", { cx: 0, cy: 0, r: 5, class: "world-marker-shape marker-plan-start" }));
|
||||||
|
} else if (kind === "gear-switch" || kind === "gear-switch-end") {
|
||||||
|
group.append(svg("path", { d: "M 0 -6 L 6 0 L 0 6 L -6 0 Z", class: "world-marker-shape marker-gear-switch" }));
|
||||||
|
} else if (kind === "final-goal") {
|
||||||
|
group.append(svg("path", { d: "M -6 -6 L 6 -6 L 6 6 L -6 6 Z", class: "world-marker-shape marker-final-goal" }));
|
||||||
|
} else {
|
||||||
|
group.append(svg("path", { d: "M 0 -6 L 6 0 L 0 6 L -6 0 Z", class: "world-marker-shape world-marker" }));
|
||||||
|
}
|
||||||
|
overlay.append(group);
|
||||||
|
const labelY = kind === "plan-start" ? 17 : -9;
|
||||||
|
const labelGroup = svg("g", { transform: `translate(${marker.position.x} ${-marker.position.y}) scale(${metrics.unitsPerPixel})` });
|
||||||
|
const label = svg("text", { x: 10, y: labelY, class: "world-marker-label" });
|
||||||
|
label.textContent = marker.labelChinese || "";
|
||||||
|
labelGroup.append(label);
|
||||||
|
overlay.append(labelGroup);
|
||||||
|
}
|
||||||
|
|
||||||
function renderWorld() {
|
function renderWorld() {
|
||||||
const bounds = getBounds(); drawOccupancy(bounds); overlay.replaceChildren(); overlay.setAttribute("viewBox", `${bounds.xMin} ${-bounds.yMax} ${bounds.xMax - bounds.xMin} ${bounds.yMax - bounds.yMin}`); overlay.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
const bounds = getBounds();
|
||||||
const metrics = worldMetrics(bounds); renderWorldCoordinates(bounds, metrics);
|
const metrics = worldMetrics(bounds);
|
||||||
const snapshot = state.staticSnapshot || {}; const frame = state.frame || {};
|
drawOccupancy(bounds);
|
||||||
const addLine = (line, css) => { const path = worldPath(line.points); if (path) overlay.append(svg("path", { d: path, class: "world-line " + css })); };
|
overlay.replaceChildren();
|
||||||
const staticClass = line => { const kind = String(line.kind || "").toLowerCase(); if (kind === "global" || kind === "coarse") return "world-coarse"; if (kind.includes("local-g2") || kind.includes("g2")) return "world-local-g2"; return "world-static"; };
|
overlay.setAttribute("viewBox", `${bounds.xMin} ${-bounds.yMax} ${bounds.xMax - bounds.xMin} ${bounds.yMax - bounds.yMin}`);
|
||||||
const dynamicClass = line => { const kind = String(line.kind || "").toLowerCase(); if (kind === "current") return "world-current"; if (kind === "previous" || line.lineStyle === 1) return "world-previous"; return "world-current"; };
|
overlay.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
||||||
safeArray(snapshot.staticPolylines).forEach(line => addLine(line, staticClass(line)));
|
renderWorldCoordinates(bounds, metrics);
|
||||||
safeArray(snapshot.directionSegments).forEach(segment => addLine(segment, segment.segmentIndex === frame.activeSegmentIndex ? "world-segment-active" : "world-direction-inactive"));
|
const snapshot = state.staticSnapshot || {};
|
||||||
safeArray(frame.dynamicPolylines).forEach(line => addLine(line, dynamicClass(line)));
|
const frame = state.frame || {};
|
||||||
const hasPath = safeArray(snapshot.staticPolylines).concat(safeArray(snapshot.directionSegments), safeArray(frame.dynamicPolylines))
|
const addLine = (line, css) => {
|
||||||
.some(line => safeArray(line && line.points).some(point => point && finite(point.x) && finite(point.y)));
|
const path = worldPath(line.points);
|
||||||
const appendMarker = marker => {
|
if (path) overlay.append(svg("path", { d: path, class: "world-line " + css }));
|
||||||
if (!marker.position || !finite(marker.position.x) || !finite(marker.position.y)) return;
|
|
||||||
const x = marker.position.x, y = -marker.position.y; const kind = String(marker.kind || "").toLowerCase();
|
|
||||||
if (kind === "vehicle") {
|
|
||||||
const pose = frame.vehiclePose; const heading = pose && finite(pose.headingRadians) ? pose.headingRadians : 0;
|
|
||||||
const cos = Math.cos(heading), sin = Math.sin(heading);
|
|
||||||
const corners = [[.38, .18], [-.38, .18], [-.38, -.18], [.38, -.18]].map(([dx, dy]) => {
|
|
||||||
const wx = marker.position.x + dx * cos - dy * sin; const wy = marker.position.y + dx * sin + dy * cos; return `${wx} ${-wy}`;
|
|
||||||
});
|
|
||||||
const group = svg("g", { class: "marker-vehicle" });
|
|
||||||
group.append(svg("path", { d: `M ${corners[0]} L ${corners[1]} L ${corners[2]} L ${corners[3]} Z`, class: "vehicle-outline" }));
|
|
||||||
group.append(svg("line", { x1: x, y1: y, x2: x + .22 * cos, y2: y - .22 * sin, class: "vehicle-heading" }));
|
|
||||||
overlay.append(group);
|
|
||||||
} else if (kind === "plan-start") {
|
|
||||||
overlay.append(svg("circle", { cx: x, cy: y, r: ".12", class: "marker-plan-start" }));
|
|
||||||
} else if (kind === "gear-switch" || kind === "gear-switch-end") {
|
|
||||||
const size = ".14"; overlay.append(svg("path", { d: `M ${x} ${y - size} L ${x + size} ${y} L ${x} ${y + size} L ${x - size} ${y} Z`, class: "marker-gear-switch" }));
|
|
||||||
} else if (kind === "final-goal") {
|
|
||||||
const size = ".13"; overlay.append(svg("path", { d: `M ${x - size} ${y - size} L ${x + size} ${y - size} L ${x + size} ${y + size} L ${x - size} ${y + size} Z`, class: "marker-final-goal" }));
|
|
||||||
} else {
|
|
||||||
const size = ".18"; overlay.append(svg("path", { d: `M ${x} ${y - size} L ${x + size} ${y} L ${x} ${y + size} L ${x - size} ${y} Z`, class: "world-marker" }));
|
|
||||||
}
|
|
||||||
const label = svg("text", { x: x + .2, y: y - .2, fill: "#20252b", "font-size": ".32" }); label.textContent = marker.labelChinese || ""; overlay.append(label);
|
|
||||||
};
|
};
|
||||||
safeArray(snapshot.staticMarkers).concat(safeArray(frame.dynamicMarkers)).forEach(appendMarker);
|
const staticLines = safeArray(snapshot.staticPolylines);
|
||||||
|
staticLines.filter(line => lineKind(line) === "global" || lineKind(line) === "coarse")
|
||||||
|
.forEach(line => addLine(line, "world-coarse"));
|
||||||
|
staticLines.filter(line => lineKind(line).includes("local-g2") || lineKind(line).includes("g2"))
|
||||||
|
.forEach(line => addLine(line, "world-local-g2"));
|
||||||
|
staticLines.filter(line => lineKind(line) !== "global" && lineKind(line) !== "coarse" && !lineKind(line).includes("local-g2") && !lineKind(line).includes("g2"))
|
||||||
|
.forEach(line => addLine(line, "world-static"));
|
||||||
|
safeArray(snapshot.directionSegments).forEach(segment => addLine(segment,
|
||||||
|
segment.segmentIndex === frame.activeSegmentIndex ? "world-segment-active" : "world-direction-inactive"));
|
||||||
|
const dynamicLines = safeArray(frame.dynamicPolylines);
|
||||||
|
dynamicLines.filter(line => lineKind(line) === "active-segment")
|
||||||
|
.forEach(line => addLine(line, "world-segment-active"));
|
||||||
|
dynamicLines.filter(line => lineKind(line) === "previous" || line.lineStyle === 1)
|
||||||
|
.forEach(line => addLine(line, "world-previous"));
|
||||||
|
dynamicLines.filter(line => lineKind(line) !== "active-segment" && lineKind(line) !== "previous" && line.lineStyle !== 1)
|
||||||
|
.forEach(line => addLine(line, "world-current"));
|
||||||
|
const allLines = staticLines.concat(safeArray(snapshot.directionSegments), dynamicLines);
|
||||||
|
const hasPath = allLines.some(line => safeArray(line && line.points)
|
||||||
|
.some(point => point && finite(point.x) && finite(point.y)));
|
||||||
|
safeArray(snapshot.staticMarkers).concat(safeArray(frame.dynamicMarkers))
|
||||||
|
.forEach(marker => appendWorldMarker(marker, frame, metrics));
|
||||||
if (!hasPath) {
|
if (!hasPath) {
|
||||||
const empty = svg("text", { x: (bounds.xMin + bounds.xMax) / 2, y: -((bounds.yMin + bounds.yMax) / 2), class: "world-empty-state", "text-anchor": "middle" });
|
const emptyGroup = svg("g", { transform: `translate(${(bounds.xMin + bounds.xMax) / 2} ${-((bounds.yMin + bounds.yMax) / 2)}) scale(${metrics.unitsPerPixel})` });
|
||||||
empty.textContent = "未收到 Local G2 路径"; overlay.append(empty);
|
const empty = svg("text", { x: 0, y: 0, class: "world-empty-state", "text-anchor": "middle" });
|
||||||
|
empty.textContent = "未收到 Local G2 路径";
|
||||||
|
emptyGroup.append(empty);
|
||||||
|
overlay.append(emptyGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,6 +363,14 @@
|
|||||||
function receiveFrame(payload) { state.frame = payload.snapshot || null; state.history = safeArray(payload.history); state.lastFrameAt = Date.now(); if (!state.ended) setLiveState(state.frame && state.frame.sessionStateChinese || "运行中"); renderAll(); }
|
function receiveFrame(payload) { state.frame = payload.snapshot || null; state.history = safeArray(payload.history); state.lastFrameAt = Date.now(); if (!state.ended) setLiveState(state.frame && state.frame.sessionStateChinese || "运行中"); renderAll(); }
|
||||||
function startEvents() { const events = new EventSource(authorized("/api/events")); events.addEventListener("frame", event => { try { receiveFrame(JSON.parse(event.data)); } catch (error) { state.redrawEnabled[state.activeTab] = false; setLiveState("页面绘图异常", "failure"); } }); events.addEventListener("end", () => { state.ended = true; events.close(); setLiveState("会话已结束"); }); events.onerror = () => { if (!state.ended) setLiveState("连接中断,正在重连", "notice"); }; }
|
function startEvents() { const events = new EventSource(authorized("/api/events")); events.addEventListener("frame", event => { try { receiveFrame(JSON.parse(event.data)); } catch (error) { state.redrawEnabled[state.activeTab] = false; setLiveState("页面绘图异常", "failure"); } }); events.addEventListener("end", () => { state.ended = true; events.close(); setLiveState("会话已结束"); }); events.onerror = () => { if (!state.ended) setLiveState("连接中断,正在重连", "notice"); }; }
|
||||||
async function boot() { installTabs(); ["overview", "ls-st", "kinematics", "history-config"].forEach(tab => { state.redrawEnabled[tab] = true; }); try { const response = await fetch(authorized("/api/bootstrap"), { cache: "no-store" }); if (!response.ok) throw new Error("bootstrap failed"); const bootstrap = await response.json(); state.staticSnapshot = bootstrap.staticSnapshot || bootstrap; state.refreshRateHz = finite(bootstrap.refreshRateHz) && bootstrap.refreshRateHz > 0 ? bootstrap.refreshRateHz : 10; renderAll(); startEvents(); window.setInterval(updateConnectionState, 250); } catch (error) { setLiveState("页面绘图异常", "failure"); } }
|
async function boot() { installTabs(); ["overview", "ls-st", "kinematics", "history-config"].forEach(tab => { state.redrawEnabled[tab] = true; }); try { const response = await fetch(authorized("/api/bootstrap"), { cache: "no-store" }); if (!response.ok) throw new Error("bootstrap failed"); const bootstrap = await response.json(); state.staticSnapshot = bootstrap.staticSnapshot || bootstrap; state.refreshRateHz = finite(bootstrap.refreshRateHz) && bootstrap.refreshRateHz > 0 ? bootstrap.refreshRateHz : 10; renderAll(); startEvents(); window.setInterval(updateConnectionState, 250); } catch (error) { setLiveState("页面绘图异常", "failure"); } }
|
||||||
|
let overviewResizeFrame = 0;
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
if (overviewResizeFrame || state.activeTab !== "overview" || !state.redrawEnabled.overview) return;
|
||||||
|
overviewResizeFrame = window.requestAnimationFrame(() => {
|
||||||
|
overviewResizeFrame = 0;
|
||||||
|
renderWorld();
|
||||||
|
});
|
||||||
|
});
|
||||||
window.addEventListener("error", () => { state.redrawEnabled[state.activeTab] = false; setLiveState("页面绘图异常", "failure"); }); window.addEventListener("unhandledrejection", () => { state.redrawEnabled[state.activeTab] = false; setLiveState("页面绘图异常", "failure"); });
|
window.addEventListener("error", () => { state.redrawEnabled[state.activeTab] = false; setLiveState("页面绘图异常", "failure"); }); window.addEventListener("unhandledrejection", () => { state.redrawEnabled[state.activeTab] = false; setLiveState("页面绘图异常", "failure"); });
|
||||||
if (window.__TRAJECTORY_VISUALIZATION_TEST__ === true) {
|
if (window.__TRAJECTORY_VISUALIZATION_TEST__ === true) {
|
||||||
["overview", "ls-st", "kinematics", "history-config"].forEach(tab => { state.redrawEnabled[tab] = true; });
|
["overview", "ls-st", "kinematics", "history-config"].forEach(tab => { state.redrawEnabled[tab] = true; });
|
||||||
|
|||||||
@@ -45,6 +45,24 @@ internal static class WebAssetChecks
|
|||||||
"overview renders its own metric coordinate system");
|
"overview renders its own metric coordinate system");
|
||||||
Verification.True(js.Contains("X (m)") && js.Contains("Y (m)"),
|
Verification.True(js.Contains("X (m)") && js.Contains("Y (m)"),
|
||||||
"overview labels both metric axes");
|
"overview labels both metric axes");
|
||||||
|
Verification.True(css.Contains("--world-smoothed-path"),
|
||||||
|
"overview defines a dedicated smoothed-path color");
|
||||||
|
Verification.True(css.Contains("--world-em-trajectory"),
|
||||||
|
"overview defines a dedicated EM-path color");
|
||||||
|
Verification.True(css.Contains(".world-marker-shape"),
|
||||||
|
"overview marker shapes use a shared screen-space class");
|
||||||
|
Verification.True(css.Contains(".marker-smooth-start"),
|
||||||
|
"overview styles the smoothed-path start marker");
|
||||||
|
Verification.True(js.Contains("function markerTransform("),
|
||||||
|
"overview anchors fixed-size markers in world coordinates");
|
||||||
|
Verification.True(js.Contains("function appendWorldMarker("),
|
||||||
|
"overview centralizes fixed-size marker rendering");
|
||||||
|
Verification.True(!js.Contains("const corners = [[.38, .18]"),
|
||||||
|
"vehicle marker no longer uses a large world-size rectangle");
|
||||||
|
Verification.True(!css.Contains(".marker-vehicle .vehicle-outline { fill: none; stroke: #20252b; stroke-width: .7; }"),
|
||||||
|
"vehicle outline no longer uses a world-scale heavy stroke");
|
||||||
|
Verification.True(!css.Contains(".marker-vehicle .vehicle-heading { stroke: #20252b; stroke-width: .8; }"),
|
||||||
|
"vehicle marker no longer uses world-scale heavy strokes");
|
||||||
Verification.True(css.Contains(".marker-vehicle"), "vehicle marker semantic class");
|
Verification.True(css.Contains(".marker-vehicle"), "vehicle marker semantic class");
|
||||||
Verification.True(css.Contains(".marker-plan-start"), "plan start marker semantic class");
|
Verification.True(css.Contains(".marker-plan-start"), "plan start marker semantic class");
|
||||||
Verification.True(css.Contains(".marker-gear-switch"), "gear-switch marker semantic class");
|
Verification.True(css.Contains(".marker-gear-switch"), "gear-switch marker semantic class");
|
||||||
|
|||||||
Reference in New Issue
Block a user