fix: 修复打砖块游戏碰撞穿透bug,添加渐进提速机制

This commit is contained in:
lenn
2026-04-29 15:43:56 +08:00
parent 26533f6916
commit 326f07ed4f
23 changed files with 786 additions and 376 deletions

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { onMount } from "svelte";
import type { HudSummary } from "$lib/types/hud";
export let summary: HudSummary;
@@ -6,6 +7,25 @@
export let panelIndex = 0;
export let xValues: number[] | null = null;
export let yValues: number[] | null = null;
export let locale: "zh-CN" | "en-US" = "zh-CN";
export let sessionStartedAt: number = Date.now();
export let isRealtime = false;
let currentTimeSeconds = 0;
let timerId: ReturnType<typeof setInterval> | null = null;
onMount(() => {
timerId = setInterval(() => {
currentTimeSeconds = Math.round((Date.now() - sessionStartedAt) / 100) / 10;
}, 200);
return () => {
if (timerId != null) clearInterval(timerId);
};
});
$: i18n = locale === "zh-CN"
? { now: "当前", min: "最小", max: "最大", waiting: "等待数据" }
: { now: "Now", min: "Min", max: "Max", waiting: "Waiting" };
const viewportWidth = 120;
const viewportHeight = 48;
@@ -50,7 +70,12 @@
}
if (axis === "x") {
return String(Math.round(value));
if (value < 60) {
return `${value.toFixed(1)}s`;
}
const mins = Math.floor(value / 60);
const secs = value - mins * 60;
return `${mins}:${secs.toFixed(0).padStart(2, "0")}`;
}
return `${Math.round(value)} N`;
@@ -104,14 +129,51 @@
return [];
}
let previousX = 0;
return rawYValues.map((rawY, index) => {
const x = rawXValues[index];
const y = Number.isFinite(rawY) ? Number(rawY) : 0;
const resolvedX = Number.isFinite(x) ? Number(x) : index + 1;
return { x: resolvedX, y };
const fallbackX = index === 0 ? 0 : previousX + 1;
const resolvedX = Number.isFinite(x) ? Number(x) : fallbackX;
const normalizedX = index === 0 ? resolvedX : Math.max(resolvedX, previousX);
previousX = normalizedX;
return { x: normalizedX, y };
});
}
function resolveXScaleBounds(
samples: CurveSample[],
currentSeconds: number,
realtime: boolean
): { min: number; max: number } {
if (samples.length === 0) {
return { min: 0, max: 1 };
}
const values = samples.map((sample) => sample.x);
const dataBounds = resolveBounds(values);
if (!realtime) {
return dataBounds;
}
const firstX = samples[0].x;
const lastX = samples[samples.length - 1].x;
const axisMax = Math.max(lastX, currentSeconds);
const positiveDiffs = samples
.slice(1)
.map((sample, index) => sample.x - samples[index].x)
.filter((diff) => diff > 0);
const averageSpacing =
positiveDiffs.length > 0 ? positiveDiffs.reduce((sum, diff) => sum + diff, 0) / positiveDiffs.length : 1;
const dataSpan = Math.max(lastX - firstX, 0);
const windowSpan = Math.max(dataSpan, averageSpacing * Math.max(samples.length - 1, 1), 1);
const axisMin = Math.max(0, axisMax - windowSpan);
return resolveBounds([axisMin, axisMax]);
}
function convertPoints(
samples: CurveSample[],
xBounds: { min: number; max: number },
@@ -146,14 +208,14 @@
}));
}
function buildXAxisTicks(samples: CurveSample[], xScaleBounds: { min: number; max: number }): AxisTick[] {
if (!samples.length) {
function buildXAxisTicks(xScaleBounds: { min: number; max: number }): AxisTick[] {
if (!Number.isFinite(xScaleBounds.min) || !Number.isFinite(xScaleBounds.max)) {
return [];
}
const first = samples[0].x;
const middle = samples[Math.floor((samples.length - 1) / 2)].x;
const last = samples[samples.length - 1].x;
const first = xScaleBounds.min;
const middle = xScaleBounds.min + (xScaleBounds.max - xScaleBounds.min) / 2;
const last = xScaleBounds.max;
const tickValues = [first, middle, last];
return tickValues.map((value) => ({
value,
@@ -185,9 +247,16 @@
$: sourceYValues = yValues && yValues.length ? yValues : summary.points;
$: sourceXValues = xValues && xValues.length ? xValues : summary.xValues ?? [];
$: samples = buildSamples(sourceYValues, sourceXValues);
$: samples = (() => {
const base = buildSamples(sourceYValues, sourceXValues);
if (isRealtime && base.length > 0 && currentTimeSeconds > 0) {
const lastSample = base[base.length - 1];
base[base.length - 1] = { ...lastSample, x: Math.max(lastSample.x, currentTimeSeconds) };
}
return base;
})();
$: sampleCount = samples.length;
$: xScaleBounds = resolveBounds(samples.map((sample) => sample.x));
$: xScaleBounds = resolveXScaleBounds(samples, currentTimeSeconds, isRealtime);
$: yScaleBounds = fixedYBounds;
$: xDataBounds = resolveDataBounds(samples.map((sample) => sample.x));
$: yDataBounds = resolveDataBounds(samples.map((sample) => sample.y));
@@ -196,7 +265,7 @@
$: areaPath = createAreaPath(plotPoints);
$: lastPoint = plotPoints.length > 0 ? plotPoints[plotPoints.length - 1] : null;
$: yAxisTicks = sampleCount > 0 ? buildYAxisTicks(yScaleBounds, yDataBounds) : [];
$: xAxisTicks = sampleCount > 0 ? buildXAxisTicks(samples, xScaleBounds) : [];
$: xAxisTicks = sampleCount > 0 ? buildXAxisTicks(xScaleBounds) : [];
$: latestValue = formatValue(summary.latest);
$: minValue = formatValue(summary.min);
$: maxValue = formatValue(summary.max);
@@ -270,7 +339,7 @@
{#if sampleCount === 0}
<div class="empty-state">
<span>Waiting</span>
<span>{i18n.waiting}</span>
</div>
{/if}
</div>
@@ -278,17 +347,17 @@
<footer class="panel-foot">
<p class="foot-item">
<span class="dot tone-cyan"></span>
<span class="metric-text">Now</span>
<span class="metric-text">{i18n.now}</span>
<span class="value">{latestValue}</span>
</p>
<p class="foot-item">
<span class="dot tone-lime"></span>
<span class="metric-text">Min</span>
<span class="metric-text">{i18n.min}</span>
<span class="value">{minValue}</span>
</p>
<p class="foot-item">
<span class="dot tone-orange"></span>
<span class="metric-text">Max</span>
<span class="metric-text">{i18n.max}</span>
<span class="value">{maxValue}</span>
</p>
</footer>
@@ -300,12 +369,10 @@
--enter-ms: 1800ms;
--fade-ms: 1000ms;
overflow: hidden;
inline-size: min(100%, clamp(29rem, 38vw, 37rem));
aspect-ratio: 1.42 / 1;
min-block-size: 20.5rem;
inline-size: min(100%, clamp(34rem, 44vw, 44rem));
justify-self: start;
display: grid;
grid-template-rows: auto auto auto;
grid-template-rows: auto 1fr auto;
gap: 0.68rem;
padding: 0.88rem 0.96rem 1rem;
border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.42);
@@ -404,6 +471,7 @@
.chart-stage {
position: relative;
block-size: clamp(12rem, 15.5vw, 15rem);
min-block-size: 5rem;
overflow: hidden;
border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.32);
border-radius: 0.62rem;
@@ -474,25 +542,29 @@
.panel-foot {
display: flex;
flex-wrap: wrap;
flex-wrap: nowrap;
gap: 0.8rem;
margin: 0;
padding: 0;
flex-shrink: 0;
}
.foot-item {
margin: 0;
display: inline-flex;
align-items: center;
gap: 0.28rem;
gap: 0.22rem;
color: rgb(var(--hud-text-main-rgb) / 0.9);
font-size: 0.76rem;
letter-spacing: 0.04em;
font-size: 0.68rem;
letter-spacing: 0.03em;
white-space: nowrap;
flex-shrink: 0;
}
.metric-text {
color: rgb(var(--hud-text-dim-rgb) / 0.82);
text-transform: uppercase;
flex-shrink: 0;
}
.dot {
@@ -519,16 +591,18 @@
@media (max-width: 1180px) {
.signal-panel {
inline-size: min(100%, clamp(24rem, 36vw, 31rem));
aspect-ratio: 1.48 / 1;
min-block-size: 17rem;
inline-size: min(100%, clamp(28rem, 40vw, 38rem));
}
.chart-stage {
block-size: clamp(10rem, 13vw, 12rem);
}
}
@media (max-height: 900px) {
.signal-panel {
inline-size: min(100%, clamp(24rem, 33vw, 30rem));
min-block-size: 16.8rem;
inline-size: min(100%, clamp(28rem, 38vw, 36rem));
padding: 0.7rem 0.76rem 0.8rem;
}
.chart-stage {
@@ -538,46 +612,31 @@
@media (max-height: 760px) {
.signal-panel {
inline-size: min(100%, clamp(21rem, 29vw, 26rem));
min-block-size: 14.4rem;
padding: 0.7rem 0.76rem 0.8rem;
}
.panel-foot {
margin-block-start: 0.28rem;
inline-size: min(100%, clamp(24rem, 34vw, 30rem));
padding: 0.62rem 0.68rem 0.72rem;
gap: 0.48rem;
}
.chart-stage {
block-size: clamp(8.3rem, 9.6vw, 9.8rem);
block-size: clamp(8rem, 9.5vw, 9.8rem);
}
}
@media (max-height: 680px) {
.signal-panel {
inline-size: min(100%, clamp(18.5rem, 24vw, 22.5rem));
min-block-size: 12.4rem;
padding: 0.62rem 0.66rem 0.68rem;
}
.panel-head {
margin-block-end: 0.26rem;
}
.panel-foot {
margin-block-start: 0.18rem;
gap: 0.56rem;
inline-size: min(100%, clamp(20rem, 28vw, 26rem));
padding: 0.52rem 0.58rem 0.6rem;
gap: 0.36rem;
}
.chart-stage {
block-size: clamp(7rem, 7.8vw, 8rem);
block-size: clamp(6.5rem, 8vw, 7.5rem);
}
}
@media (max-width: 900px) {
.signal-panel {
inline-size: 100%;
aspect-ratio: 1.7 / 1;
min-block-size: 0;
}
}
</style>