Files
cmr-mini/miniprogram/game/telemetry/telemetryConfig.ts

142 lines
3.9 KiB
TypeScript

export interface TelemetryConfig {
heartRateAge: number
restingHeartRateBpm: number
userWeightKg: number
}
export type HeartRateTone = 'blue' | 'purple' | 'green' | 'yellow' | 'orange' | 'red'
type HeartRateToneMeta = {
label: string
heartRateRangeText: string
speedRangeText: string
}
const HEART_RATE_TONE_META: Record<HeartRateTone, HeartRateToneMeta> = {
blue: {
label: '激活放松',
heartRateRangeText: '<=39%',
speedRangeText: '<3.2 km/h',
},
purple: {
label: '动态热身',
heartRateRangeText: '40~54%',
speedRangeText: '3.2~4.0 km/h',
},
green: {
label: '脂肪燃烧',
heartRateRangeText: '55~69%',
speedRangeText: '4.1~5.5 km/h',
},
yellow: {
label: '糖分消耗',
heartRateRangeText: '70~79%',
speedRangeText: '5.6~7.1 km/h',
},
orange: {
label: '心肺训练',
heartRateRangeText: '80~89%',
speedRangeText: '7.2~8.8 km/h',
},
red: {
label: '峰值锻炼',
heartRateRangeText: '>=90%',
speedRangeText: '>=8.9 km/h',
},
}
export function clampTelemetryAge(age: number): number {
if (!Number.isFinite(age)) {
return 30
}
return Math.max(10, Math.min(85, Math.round(age)))
}
export function estimateRestingHeartRateBpm(age: number): number {
const safeAge = clampTelemetryAge(age)
const estimated = 68 + (safeAge - 30) * 0.12
return Math.max(56, Math.min(76, Math.round(estimated)))
}
export function normalizeRestingHeartRateBpm(restingHeartRateBpm: number, age: number): number {
if (!Number.isFinite(restingHeartRateBpm) || restingHeartRateBpm <= 0) {
return estimateRestingHeartRateBpm(age)
}
return Math.max(40, Math.min(95, Math.round(restingHeartRateBpm)))
}
export function normalizeUserWeightKg(userWeightKg: number): number {
if (!Number.isFinite(userWeightKg) || userWeightKg <= 0) {
return 65
}
return Math.max(35, Math.min(180, Math.round(userWeightKg)))
}
export const DEFAULT_TELEMETRY_CONFIG: TelemetryConfig = {
heartRateAge: 30,
restingHeartRateBpm: estimateRestingHeartRateBpm(30),
userWeightKg: 65,
}
export function mergeTelemetryConfig(overrides?: Partial<TelemetryConfig> | null): TelemetryConfig {
const heartRateAge = overrides && overrides.heartRateAge !== undefined
? clampTelemetryAge(overrides.heartRateAge)
: DEFAULT_TELEMETRY_CONFIG.heartRateAge
const restingHeartRateBpm = overrides && overrides.restingHeartRateBpm !== undefined
? normalizeRestingHeartRateBpm(overrides.restingHeartRateBpm, heartRateAge)
: estimateRestingHeartRateBpm(heartRateAge)
const userWeightKg = overrides && overrides.userWeightKg !== undefined
? normalizeUserWeightKg(overrides.userWeightKg)
: DEFAULT_TELEMETRY_CONFIG.userWeightKg
return {
heartRateAge,
restingHeartRateBpm,
userWeightKg,
}
}
export function getHeartRateToneSampleBpm(tone: HeartRateTone, config: TelemetryConfig): number {
const maxHeartRate = Math.max(120, 220 - config.heartRateAge)
const restingHeartRate = Math.min(maxHeartRate - 15, config.restingHeartRateBpm)
const reserve = Math.max(20, maxHeartRate - restingHeartRate)
if (tone === 'blue') {
return Math.round(restingHeartRate + reserve * 0.3)
}
if (tone === 'purple') {
return Math.round(restingHeartRate + reserve * 0.47)
}
if (tone === 'green') {
return Math.round(restingHeartRate + reserve * 0.62)
}
if (tone === 'yellow') {
return Math.round(restingHeartRate + reserve * 0.745)
}
if (tone === 'orange') {
return Math.round(restingHeartRate + reserve * 0.845)
}
return Math.round(restingHeartRate + reserve * 0.93)
}
export function getHeartRateToneLabel(tone: HeartRateTone): string {
return HEART_RATE_TONE_META[tone].label
}
export function getHeartRateToneRangeText(tone: HeartRateTone): string {
return HEART_RATE_TONE_META[tone].heartRateRangeText
}
export function getSpeedToneRangeText(tone: HeartRateTone): string {
return HEART_RATE_TONE_META[tone].speedRangeText
}