diff --git a/src/lib/components/CenterStage.svelte b/src/lib/components/CenterStage.svelte index 896d090..54110c0 100644 --- a/src/lib/components/CenterStage.svelte +++ b/src/lib/components/CenterStage.svelte @@ -29,6 +29,7 @@ export let summary: HudSummary; export let pressureMatrix: number[] | null = null; export let spatialForce: HudSpatialForce | null = null; + export let devkitSpatialForce: HudSpatialForce | null = null; export let showConfigPanel = false; export let configPanelTitle = ""; export let configPanelHint = ""; @@ -327,6 +328,29 @@ {locale} side="right" panelIndex={rightPanels.length} + panelCode="ALG" + panelTitle={locale === "zh-CN" ? "本地切向力" : "Local Tangential"} + badgeLabel={locale === "zh-CN" ? "算法" : "ALGO"} + /> + + +
+
diff --git a/src/lib/components/SpatialForcePanel.svelte b/src/lib/components/SpatialForcePanel.svelte index 6476cd1..551294a 100644 --- a/src/lib/components/SpatialForcePanel.svelte +++ b/src/lib/components/SpatialForcePanel.svelte @@ -5,6 +5,13 @@ export let side: "left" | "right" = "right"; export let panelIndex = 0; export let locale: "zh-CN" | "en-US" = "zh-CN"; + export let panelCode = "TAN"; + export let panelTitle = ""; + export let badgeLabel = ""; + export let badgeTone: "cyan" | "lime" | "orange" = "cyan"; + export let showMetrics = true; + export let requireMagnitude = true; + export let compactMetaText = ""; function formatValue(value: number | null, digits = 1): string { if (value === null || !Number.isFinite(value)) { @@ -91,11 +98,15 @@ strength: "Strength", confidence: "Confidence" }; + $: resolvedTitle = panelTitle || i18n.title; + $: resolvedBadgeLabel = badgeLabel || i18n.angle; + $: resolvedCompactMetaText = + compactMetaText || (locale === "zh-CN" ? "仅使用角度流" : "Angle stream only"); $: hasData = spatialForce !== null && Number.isFinite(spatialForce.angleDeg) && - Number.isFinite(spatialForce.magnitude); + (!requireMagnitude || Number.isFinite(spatialForce.magnitude)); $: angleDeg = hasData ? normalizeAngle(spatialForce?.angleDeg ?? 0) : 0; $: updateVisualAngle(angleDeg, hasData); $: magnitude = hasData ? spatialForce?.magnitude ?? 0 : null; @@ -110,12 +121,12 @@ >
-

TAN

-

{i18n.title}

+

{panelCode}

+

{resolvedTitle}

@@ -152,8 +163,13 @@

{i18n.heading}

-

{i18n.strength}: {formatValue(magnitude, 2)}

-

{i18n.confidence}: {hasData ? `${formatValue(confidence, 0)}%` : "--"}

+ {#if showMetrics} +

{i18n.strength}: {formatValue(magnitude, 2)}

+

{i18n.confidence}: {hasData ? `${formatValue(confidence, 0)}%` : "--"}

+ {:else} +

{resolvedCompactMetaText}

+

{hasData ? (locale === "zh-CN" ? "实时对比中" : "Live comparison") : "--"}

+ {/if}
@@ -251,6 +267,14 @@ border-color: rgb(var(--hud-cyan-rgb) / 0.54); } + .icon-chip.tone-lime { + border-color: rgb(var(--hud-lime-rgb) / 0.54); + } + + .icon-chip.tone-orange { + border-color: rgb(var(--hud-orange-rgb) / 0.54); + } + .panel-body { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(10rem, 0.9fr); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 258f62a..7bb9edb 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -46,6 +46,13 @@ dtsMs: number; } + interface DevKitPztAngleEvent { + seq: number; + timestampMs: number; + dtsMs: number; + angle: number; + } + const copyByLocale: Record = { "zh-CN": { appName: "JE-Skin", @@ -230,6 +237,7 @@ let summary: HudSummary = buildEmptySummary(); let pressureMatrix: number[] | null = null; let spatialForce: HudSpatialForce | null = null; + let devkitSpatialForce: HudSpatialForce | null = null; let matrixRows = 12; let matrixCols = 7; let rangeMin = DEFAULT_PRESSURE_RANGE_MIN; @@ -270,6 +278,7 @@ rowsKept: number; } | null = null; let devkitStatusTimer: number | null = null; + let devkitSpatialForceClearTimer: number | null = null; let sessionStartedAt: number = Date.now(); $: uiCopy = copyByLocale[locale]; @@ -297,6 +306,31 @@ return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; } + function clearDevkitSpatialForce(): void { + devkitSpatialForce = null; + if (devkitSpatialForceClearTimer != null && typeof window !== "undefined") { + window.clearTimeout(devkitSpatialForceClearTimer); + devkitSpatialForceClearTimer = null; + } + hasSignalData = signalPanels.length > 0 || summary.points.length > 0 || spatialForce !== null; + } + + function scheduleDevkitSpatialForceClear(): void { + if (typeof window === "undefined") { + return; + } + + if (devkitSpatialForceClearTimer != null) { + window.clearTimeout(devkitSpatialForceClearTimer); + } + + devkitSpatialForceClearTimer = window.setTimeout(() => { + devkitSpatialForce = null; + devkitSpatialForceClearTimer = null; + hasSignalData = signalPanels.length > 0 || summary.points.length > 0 || spatialForce !== null; + }, 420); + } + function clamp(value: number, min: number, max: number): number { return Math.min(max, Math.max(min, value)); } @@ -720,6 +754,7 @@ function resetReplayVisualState(): void { pressureMatrix = buildZeroMatrix(); spatialForce = null; + clearDevkitSpatialForce(); signalPanels = buildInactivePanels(); summary = buildEmptySummary(); hasSignalData = false; @@ -756,6 +791,7 @@ replayProgress = replayFrames.length > 1 ? safeIndex / (replayFrames.length - 1) : 1; pressureMatrix = frameValuesToMatrix(replayFrames[safeIndex].values); spatialForce = null; + clearDevkitSpatialForce(); signalPanels = buildInactivePanels(); summary = buildReplaySummaryAt(safeIndex); hasSignalData = true; @@ -1011,7 +1047,11 @@ } pressureMatrix = packet.pressureMatrix; spatialForce = packet.spatialForce ?? null; - hasSignalData = signalPanels.length > 0 || packet.summary.points.length > 0 || spatialForce !== null; + hasSignalData = + signalPanels.length > 0 || + packet.summary.points.length > 0 || + spatialForce !== null || + devkitSpatialForce !== null; } function clearHudPanels(): void { @@ -1020,6 +1060,7 @@ summary = buildEmptySummary(); pressureMatrix = null; spatialForce = null; + clearDevkitSpatialForce(); } function startMockFeed(push: (packet: HudPacket) => void): () => void { @@ -1794,12 +1835,25 @@ .catch((error) => { console.error("Failed to listen for hud_stream:", error); }); - void listen<{ seq: number; timestampMs: number; dtsMs: number; angle: number }>( - "devkit_pzt_angle", - (event) => { - console.log("[devkit_pzt_angle]", event.payload); + void listen("devkit_pzt_angle", (event) => { + const angleDeg = Number(event.payload.angle); + if (!Number.isFinite(angleDeg)) { + clearDevkitSpatialForce(); + return; } - ) + + devkitSpatialForce = { + angleDeg, + magnitude: 0, + confidence: 0 + }; + scheduleDevkitSpatialForceClear(); + hasSignalData = + signalPanels.length > 0 || + summary.points.length > 0 || + spatialForce !== null || + devkitSpatialForce !== null; + }) .then((unlisten) => { if (disposed) { unlisten(); @@ -1818,6 +1872,7 @@ return () => { disposed = true; pauseReplayPlayback(); + clearDevkitSpatialForce(); stopMockFeed?.(); unlistenHudStream?.(); unlistenDevkitPztAngle?.(); @@ -1937,6 +1992,7 @@ rightPanels={rightSignalPanels} {pressureMatrix} {spatialForce} + {devkitSpatialForce} showConfigPanel={isConfigPanelOpen} showPrecisionTestPanel={isPrecisionTestOpen} {summary}