Add mock GPS simulator and configurable location sources

This commit is contained in:
2026-03-24 14:24:53 +08:00
parent 0295893b56
commit 2cf0bb76b4
16 changed files with 2575 additions and 122 deletions

View File

@@ -121,6 +121,13 @@ export interface MapEngineViewState {
statusText: string
gpsTracking: boolean
gpsTrackingText: string
locationSourceMode: 'real' | 'mock'
locationSourceText: string
mockBridgeConnected: boolean
mockBridgeStatusText: string
mockBridgeUrlText: string
mockCoordText: string
mockSpeedText: string
gpsCoordText: string
heartRateConnected: boolean
heartRateStatusText: string
@@ -209,6 +216,13 @@ const VIEW_SYNC_KEYS: Array<keyof MapEngineViewState> = [
'statusText',
'gpsTracking',
'gpsTrackingText',
'locationSourceMode',
'locationSourceText',
'mockBridgeConnected',
'mockBridgeStatusText',
'mockBridgeUrlText',
'mockCoordText',
'mockSpeedText',
'gpsCoordText',
'heartRateConnected',
'heartRateStatusText',
@@ -582,15 +596,20 @@ export class MapEngine {
this.setState({
gpsTracking: this.locationController.listening,
gpsTrackingText: message,
...this.getLocationControllerViewPatch(),
}, true)
},
onError: (message) => {
this.setState({
gpsTracking: false,
gpsTracking: this.locationController.listening,
gpsTrackingText: message,
...this.getLocationControllerViewPatch(),
statusText: `${message} (${this.buildVersion})`,
}, true)
},
onDebugStateChange: () => {
this.setState(this.getLocationControllerViewPatch(), true)
},
})
this.heartRateController = new HeartRateController({
onHeartRate: (bpm) => {
@@ -716,6 +735,13 @@ export class MapEngine {
statusText: `单 WebGL 管线已就绪,等待传感器接入 (${this.buildVersion})`,
gpsTracking: false,
gpsTrackingText: '持续定位待启动',
locationSourceMode: 'real',
locationSourceText: '真实定位',
mockBridgeConnected: false,
mockBridgeStatusText: '未连接',
mockBridgeUrlText: 'wss://gs.gotomars.xyz/mock-gps',
mockCoordText: '--',
mockSpeedText: '--',
gpsCoordText: '--',
heartRateConnected: false,
heartRateStatusText: '心率带未连接',
@@ -833,6 +859,20 @@ export class MapEngine {
return this.gamePresentation.hud.hudTargetControlId
}
getLocationControllerViewPatch(): Partial<MapEngineViewState> {
const debugState = this.locationController.getDebugState()
return {
gpsTracking: debugState.listening,
locationSourceMode: debugState.sourceMode,
locationSourceText: debugState.sourceModeText,
mockBridgeConnected: debugState.mockBridgeConnected,
mockBridgeStatusText: debugState.mockBridgeStatusText,
mockBridgeUrlText: debugState.mockBridgeUrlText,
mockCoordText: debugState.mockCoordText,
mockSpeedText: debugState.mockSpeedText,
}
}
getGameModeText(): string {
return this.gameMode === 'score-o' ? '积分赛' : '顺序赛'
}
@@ -1272,6 +1312,26 @@ export class MapEngine {
this.locationController.start()
}
handleSetRealLocationMode(): void {
this.locationController.setSourceMode('real')
}
handleSetMockLocationMode(): void {
this.locationController.setSourceMode('mock')
}
handleConnectMockLocationBridge(): void {
this.locationController.connectMockBridge()
}
handleDisconnectMockLocationBridge(): void {
this.locationController.disconnectMockBridge()
}
handleSetMockLocationBridgeUrl(url: string): void {
this.locationController.setMockBridgeUrl(url)
}
handleSetGameMode(nextMode: 'classic-sequential' | 'score-o'): void {
if (this.gameMode === nextMode) {
return