优化指北针持续追踪手感

This commit is contained in:
2026-03-26 17:06:34 +08:00
parent 5fc996dea1
commit 8b10afe5b9
2 changed files with 92 additions and 14 deletions

View File

@@ -57,6 +57,8 @@ const AUTO_ROTATE_SNAP_DEG = 0.1
const AUTO_ROTATE_DEADZONE_DEG = 4 const AUTO_ROTATE_DEADZONE_DEG = 4
const AUTO_ROTATE_MAX_STEP_DEG = 0.75 const AUTO_ROTATE_MAX_STEP_DEG = 0.75
const AUTO_ROTATE_HEADING_SMOOTHING = 0.46 const AUTO_ROTATE_HEADING_SMOOTHING = 0.46
const COMPASS_NEEDLE_FRAME_MS = 16
const COMPASS_NEEDLE_SNAP_DEG = 0.08
const COMPASS_TUNING_PRESETS: Record<CompassTuningProfile, { const COMPASS_TUNING_PRESETS: Record<CompassTuningProfile, {
needleMinSmoothing: number needleMinSmoothing: number
needleMaxSmoothing: number needleMaxSmoothing: number
@@ -827,6 +829,7 @@ export class MapEngine {
previewResetTimer: number previewResetTimer: number
viewSyncTimer: number viewSyncTimer: number
autoRotateTimer: number autoRotateTimer: number
compassNeedleTimer: number
pendingViewPatch: Partial<MapEngineViewState> pendingViewPatch: Partial<MapEngineViewState>
mounted: boolean mounted: boolean
diagnosticUiEnabled: boolean diagnosticUiEnabled: boolean
@@ -834,6 +837,7 @@ export class MapEngine {
sensorHeadingDeg: number | null sensorHeadingDeg: number | null
smoothedSensorHeadingDeg: number | null smoothedSensorHeadingDeg: number | null
compassDisplayHeadingDeg: number | null compassDisplayHeadingDeg: number | null
targetCompassDisplayHeadingDeg: number | null
compassSource: 'compass' | 'motion' | null compassSource: 'compass' | 'motion' | null
compassTuningProfile: CompassTuningProfile compassTuningProfile: CompassTuningProfile
smoothedMovementHeadingDeg: number | null smoothedMovementHeadingDeg: number | null
@@ -1277,6 +1281,7 @@ export class MapEngine {
this.previewResetTimer = 0 this.previewResetTimer = 0
this.viewSyncTimer = 0 this.viewSyncTimer = 0
this.autoRotateTimer = 0 this.autoRotateTimer = 0
this.compassNeedleTimer = 0
this.pendingViewPatch = {} this.pendingViewPatch = {}
this.mounted = false this.mounted = false
this.diagnosticUiEnabled = false this.diagnosticUiEnabled = false
@@ -1284,6 +1289,7 @@ export class MapEngine {
this.sensorHeadingDeg = null this.sensorHeadingDeg = null
this.smoothedSensorHeadingDeg = null this.smoothedSensorHeadingDeg = null
this.compassDisplayHeadingDeg = null this.compassDisplayHeadingDeg = null
this.targetCompassDisplayHeadingDeg = null
this.compassSource = null this.compassSource = null
this.compassTuningProfile = 'balanced' this.compassTuningProfile = 'balanced'
this.smoothedMovementHeadingDeg = null this.smoothedMovementHeadingDeg = null
@@ -1399,6 +1405,7 @@ export class MapEngine {
this.clearPreviewResetTimer() this.clearPreviewResetTimer()
this.clearViewSyncTimer() this.clearViewSyncTimer()
this.clearAutoRotateTimer() this.clearAutoRotateTimer()
this.clearCompassNeedleTimer()
this.clearPunchFeedbackTimer() this.clearPunchFeedbackTimer()
this.clearContentCardTimer() this.clearContentCardTimer()
this.clearMapPulseTimer() this.clearMapPulseTimer()
@@ -2940,27 +2947,19 @@ export class MapEngine {
const compassHeadingDeg = getCompassReferenceHeadingDeg(this.northReferenceMode, this.smoothedSensorHeadingDeg) const compassHeadingDeg = getCompassReferenceHeadingDeg(this.northReferenceMode, this.smoothedSensorHeadingDeg)
if (this.compassDisplayHeadingDeg === null) { if (this.compassDisplayHeadingDeg === null) {
this.compassDisplayHeadingDeg = compassHeadingDeg this.compassDisplayHeadingDeg = compassHeadingDeg
this.targetCompassDisplayHeadingDeg = compassHeadingDeg
this.syncCompassDisplayState()
} else { } else {
this.targetCompassDisplayHeadingDeg = compassHeadingDeg
const displayDeltaDeg = Math.abs(normalizeAngleDeltaDeg(compassHeadingDeg - this.compassDisplayHeadingDeg)) const displayDeltaDeg = Math.abs(normalizeAngleDeltaDeg(compassHeadingDeg - this.compassDisplayHeadingDeg))
if (displayDeltaDeg >= COMPASS_TUNING_PRESETS[this.compassTuningProfile].displayDeadzoneDeg) { if (displayDeltaDeg >= COMPASS_TUNING_PRESETS[this.compassTuningProfile].displayDeadzoneDeg) {
this.compassDisplayHeadingDeg = interpolateAngleDeg( this.scheduleCompassNeedleFollow()
this.compassDisplayHeadingDeg,
compassHeadingDeg,
getCompassNeedleSmoothingFactor(
this.compassDisplayHeadingDeg,
compassHeadingDeg,
this.compassTuningProfile,
),
)
} }
} }
this.autoRotateHeadingDeg = this.resolveAutoRotateInputHeadingDeg() this.autoRotateHeadingDeg = this.resolveAutoRotateInputHeadingDeg()
this.setState({ this.setState({
compassNeedleDeg: formatCompassNeedleDegForMode(this.northReferenceMode, this.compassDisplayHeadingDeg), compassSourceText: formatCompassSourceText(this.compassSource),
sensorHeadingText: formatHeadingText(this.compassDisplayHeadingDeg),
compassDeclinationText: formatCompassDeclinationText(this.northReferenceMode),
...(this.diagnosticUiEnabled ...(this.diagnosticUiEnabled
? { ? {
...this.getTelemetrySensorViewPatch(), ...this.getTelemetrySensorViewPatch(),
@@ -2986,9 +2985,11 @@ export class MapEngine {
handleCompassError(message: string): void { handleCompassError(message: string): void {
this.clearAutoRotateTimer() this.clearAutoRotateTimer()
this.clearCompassNeedleTimer()
this.targetAutoRotationDeg = null this.targetAutoRotationDeg = null
this.autoRotateCalibrationPending = false this.autoRotateCalibrationPending = false
this.compassSource = null this.compassSource = null
this.targetCompassDisplayHeadingDeg = null
this.setState({ this.setState({
compassSourceText: formatCompassSourceText(null), compassSourceText: formatCompassSourceText(null),
autoRotateCalibrationText: formatAutoRotateCalibrationText(false, this.autoRotateCalibrationOffsetDeg), autoRotateCalibrationText: formatAutoRotateCalibrationText(false, this.autoRotateCalibrationOffsetDeg),
@@ -3013,6 +3014,7 @@ export class MapEngine {
this.northReferenceMode = nextMode this.northReferenceMode = nextMode
this.autoRotateCalibrationOffsetDeg = nextMapNorthOffsetDeg this.autoRotateCalibrationOffsetDeg = nextMapNorthOffsetDeg
this.compassDisplayHeadingDeg = compassHeadingDeg this.compassDisplayHeadingDeg = compassHeadingDeg
this.targetCompassDisplayHeadingDeg = compassHeadingDeg
if (this.state.orientationMode === 'north-up') { if (this.state.orientationMode === 'north-up') {
const exactCenter = this.getExactCenterFromTranslate(this.state.tileTranslateX, this.state.tileTranslateY) const exactCenter = this.getExactCenterFromTranslate(this.state.tileTranslateX, this.state.tileTranslateY)
@@ -3056,6 +3058,10 @@ export class MapEngine {
if (this.state.orientationMode === 'heading-up' && this.refreshAutoRotateTarget()) { if (this.state.orientationMode === 'heading-up' && this.refreshAutoRotateTarget()) {
this.scheduleAutoRotate() this.scheduleAutoRotate()
} }
if (this.compassDisplayHeadingDeg !== null) {
this.syncCompassDisplayState()
}
} }
setCourseHeading(headingDeg: number | null): void { setCourseHeading(headingDeg: number | null): void {
@@ -3570,6 +3576,78 @@ export class MapEngine {
this.autoRotateTimer = 0 this.autoRotateTimer = 0
} }
} }
clearCompassNeedleTimer(): void {
if (this.compassNeedleTimer) {
clearTimeout(this.compassNeedleTimer)
this.compassNeedleTimer = 0
}
}
syncCompassDisplayState(): void {
this.setState({
compassNeedleDeg: formatCompassNeedleDegForMode(this.northReferenceMode, this.compassDisplayHeadingDeg),
sensorHeadingText: formatHeadingText(this.compassDisplayHeadingDeg),
compassDeclinationText: formatCompassDeclinationText(this.northReferenceMode),
...(this.diagnosticUiEnabled
? {
...this.getTelemetrySensorViewPatch(),
northReferenceButtonText: formatNorthReferenceButtonText(this.northReferenceMode),
autoRotateSourceText: this.getAutoRotateSourceText(),
northReferenceText: formatNorthReferenceText(this.northReferenceMode),
}
: {}),
})
}
scheduleCompassNeedleFollow(): void {
if (
this.compassNeedleTimer
|| this.targetCompassDisplayHeadingDeg === null
|| this.compassDisplayHeadingDeg === null
) {
return
}
const step = () => {
this.compassNeedleTimer = 0
if (
this.targetCompassDisplayHeadingDeg === null
|| this.compassDisplayHeadingDeg === null
) {
return
}
const deltaDeg = normalizeAngleDeltaDeg(
this.targetCompassDisplayHeadingDeg - this.compassDisplayHeadingDeg,
)
const absDeltaDeg = Math.abs(deltaDeg)
if (absDeltaDeg <= COMPASS_NEEDLE_SNAP_DEG) {
if (absDeltaDeg > 0.001) {
this.compassDisplayHeadingDeg = this.targetCompassDisplayHeadingDeg
this.syncCompassDisplayState()
}
return
}
this.compassDisplayHeadingDeg = interpolateAngleDeg(
this.compassDisplayHeadingDeg,
this.targetCompassDisplayHeadingDeg,
getCompassNeedleSmoothingFactor(
this.compassDisplayHeadingDeg,
this.targetCompassDisplayHeadingDeg,
this.compassTuningProfile,
),
)
this.syncCompassDisplayState()
this.scheduleCompassNeedleFollow()
}
this.compassNeedleTimer = setTimeout(step, COMPASS_NEEDLE_FRAME_MS) as unknown as number
}
pickViewPatch(patch: Partial<MapEngineViewState>): Partial<MapEngineViewState> { pickViewPatch(patch: Partial<MapEngineViewState>): Partial<MapEngineViewState> {
const viewPatch = {} as Partial<MapEngineViewState> const viewPatch = {} as Partial<MapEngineViewState>
for (const key of VIEW_SYNC_KEYS) { for (const key of VIEW_SYNC_KEYS) {

View File

@@ -89,7 +89,7 @@ type MapPageData = MapEngineViewState & {
showRightButtonGroups: boolean showRightButtonGroups: boolean
showBottomDebugButton: boolean showBottomDebugButton: boolean
} }
const INTERNAL_BUILD_VERSION = 'map-build-282' const INTERNAL_BUILD_VERSION = 'map-build-283'
const USER_SETTINGS_STORAGE_KEY = 'cmr_user_settings_v1' const USER_SETTINGS_STORAGE_KEY = 'cmr_user_settings_v1'
const CLASSIC_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/classic-sequential.json' const CLASSIC_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/classic-sequential.json'
const SCORE_O_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/score-o.json' const SCORE_O_REMOTE_GAME_CONFIG_URL = 'https://oss-mbh5.colormaprun.com/gotomars/event/score-o.json'