From aea87e635e9f505dd961d4904b4d6d129118ba0d Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Tue, 15 Sep 2026 01:37:18 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E9=80=86=E8=B5=B0=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E3=81=AE=E7=B4=AF=E7=A9=8D=E8=B7=9D=E9=9B=A2=E3=82=92=E6=9C=80?= =?UTF-8?q?=E6=8E=A5=E8=BF=91=E7=82=B9=E3=81=8B=E3=82=89=E3=81=AE=E6=AD=A3?= =?UTF-8?q?=E5=91=B3=E8=B7=9D=E9=9B=A2=E3=81=AB=E5=A4=89=E3=81=88=E3=81=A6?= =?UTF-8?q?=E9=9D=99=E6=AD=A2=E4=B8=AD=E3=81=AE=E8=AA=A4=E6=A4=9C=E7=9F=A5?= =?UTF-8?q?=E3=82=92=E9=98=B2=E3=81=90=20(#6976)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cumulativeIncreaseRef` は正の増加だけを足し込む一方、 `WRONG_DIRECTION_NOISE_TOLERANCE` (10m) 以内の減少は無視して累積を維持していた。 この非対称性により、静止していてもGPSの揺れだけで累積が単調に伸び、 `WRONG_DIRECTION_MIN_DISTANCE` (300m) に到達しうる (#6967)。 累積を「最接近点 (`nearestDistanceRef`) からの正味距離」で測るよう変更する。 無視した小さな減少も起点の更新を通じて距離へ反映されるため、静止中の揺れでは 正味距離が伸びない。連続カウンタ側のヒステリシス (#5934 の意図) はそのまま残す。 Claude-Session: https://claude.ai/code/session_01W43wAgGXf7Sank3yirTJD2 Co-authored-by: Claude Opus 5 --- src/hooks/useWrongDirectionDetector.test.tsx | 39 ++++++++++++++++++++ src/hooks/useWrongDirectionDetector.ts | 36 ++++++++++++------ 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/hooks/useWrongDirectionDetector.test.tsx b/src/hooks/useWrongDirectionDetector.test.tsx index 431410506e..b8a0611760 100644 --- a/src/hooks/useWrongDirectionDetector.test.tsx +++ b/src/hooks/useWrongDirectionDetector.test.tsx @@ -245,6 +245,45 @@ describe('useWrongDirectionDetector (hysteresis)', () => { expect(result.current.isLoopLineWrongDirection).toBe(false); }); + it('静止したまま小さなGPSノイズで揺れ続けても逆方向と判定されない', () => { + // #6967: 増加だけを累積し、ノイズトレランス以内の減少を相殺しないと、 + // 実際には動いていなくても累積が単調に伸びて閾値に達する。 + // 0.00009 度 ≒ 8m の往復なので、位置は 8m の帯から一歩も出ていない。 + const NOISE_LON_DELTA = 0.00009; + + const store = createStore(); + seedStore(store); + + const { result } = renderDetector(store); + + // 旧ロジックでは 76 サンプル目(38 往復)で累積が 300m に達していた + for (let i = 0; i < 200; i += 1) { + act(() => + setLocationLon( + store, + i % 2 === 0 ? BASE_LON : BASE_LON - NOISE_LON_DELTA + ) + ); + } + + expect(result.current.isWrongDirection).toBe(false); + expect(result.current.isLoopLineWrongDirection).toBe(false); + }); + + it('ノイズ以下の後退を挟んでも、実際に遠ざかっていれば逆方向と判定される', () => { + const store = createStore(); + seedStore(store); + + const { result } = renderDetector(store); + + act(() => setLocationLon(store, BASE_LON)); + + // 100m 級の西進のあいだに、ノイズトレランス以内(約 8m)の東進を挟む + advanceWest(store, [-0.001, 0.00009, -0.001, -0.001, -0.001, -0.001]); + + expect(result.current.isWrongDirection).toBe(true); + }); + it('公開フックは atom を読むだけで、Effectフックを呼ばなくても初期値を返す', () => { const store = createStore(); const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( diff --git a/src/hooks/useWrongDirectionDetector.ts b/src/hooks/useWrongDirectionDetector.ts index 64df72cf48..22ce75fdf5 100644 --- a/src/hooks/useWrongDirectionDetector.ts +++ b/src/hooks/useWrongDirectionDetector.ts @@ -14,7 +14,7 @@ import { useNextStation } from './useNextStation'; // 連続何回の距離増加で逆方向と判定するか const WRONG_DIRECTION_CONSECUTIVE_COUNT = 4; -// 逆方向判定に必要な最小累積距離(m) +// 逆方向判定に必要な、最接近点からの最小の正味距離(m) const WRONG_DIRECTION_MIN_DISTANCE = 300; // GPSノイズとみなして無視する減少幅(m)。これ以下の減少では検知カウンタを維持する const WRONG_DIRECTION_NOISE_TOLERANCE = 10; @@ -56,8 +56,10 @@ export const useWrongDirectionDetectorEffect = (): void => { const prevDistanceRef = useRef(null); // 連続で距離が増加した回数 const consecutiveIncreaseCountRef = useRef(0); - // 連続増加中の累積距離(m) - const cumulativeIncreaseRef = useRef(0); + // 連続増加中に次駅へ最も近づいたときの距離(m)。「遠ざかった距離」はここからの + // 正味の差で測る。正の増加だけを足し込む累積だと、ノイズとして無視した小さな + // 減少が相殺されず、静止していてもGPSの揺れだけで単調に伸びる(#6967) + const nearestDistanceRef = useRef(null); // 通知済みのnextStation ID(undefinedは未設定、null/numberは通知済み駅ID) const notifiedForStationIdRef = useRef(undefined); // 前回のnextStation ID(駅変更検知用) @@ -89,7 +91,7 @@ export const useWrongDirectionDetectorEffect = (): void => { const resetDetectionState = useCallback(() => { prevDistanceRef.current = null; consecutiveIncreaseCountRef.current = 0; - cumulativeIncreaseRef.current = 0; + nearestDistanceRef.current = null; notifiedForStationIdRef.current = undefined; writeState(false, false); }, [writeState]); @@ -149,31 +151,43 @@ export const useWrongDirectionDetectorEffect = (): void => { const prevDistance = prevDistanceRef.current; prevDistanceRef.current = currentDistance; - // 初回測定は比較対象がないのでスキップ + // 初回測定は比較対象がないので、正味の増加を測る起点だけ置いて終了する if (prevDistance == null) { + nearestDistanceRef.current = currentDistance; return; } const increase = currentDistance - prevDistance; if (increase > 0) { - // 遠ざかった: 連続カウンタと累積距離を伸ばす + // 遠ざかった: 連続カウンタを伸ばす consecutiveIncreaseCountRef.current += 1; - cumulativeIncreaseRef.current += increase; } else if (increase < -WRONG_DIRECTION_NOISE_TOLERANCE) { - // 明確に近づいた: 連続カウンタをリセット + // 明確に近づいた: 連続カウンタをリセットし、起点も現在地へ張り直す + // (= 正味の増加を 0 に戻す)。 // ただし wrongDirectionDetected 自体は据え置き、通知発火済みフラグも維持する。 // 一度逆方向と判定された後、GPSノイズで一時的に近づいたとしても、 // 次駅が変わるか到着するまでは「逆方向状態」のまま扱う consecutiveIncreaseCountRef.current = 0; - cumulativeIncreaseRef.current = 0; + nearestDistanceRef.current = currentDistance; } - // 上記いずれにも当てはまらない小さな減少は GPS ノイズとして無視する + // 小さな減少は GPS ノイズとして連続カウンタを落とさない(ヒステリシス)。 + // ただし起点の更新は増減に関わらず行うため、距離そのものには反映される。 + + // 起点より近づいたら起点を更新する。無視した小さな減少もここで効くので、 + // 静止中の揺れでは正味の増加が伸びない + const nearestDistance = + nearestDistanceRef.current == null + ? currentDistance + : Math.min(nearestDistanceRef.current, currentDistance); + nearestDistanceRef.current = nearestDistance; + // 最接近点からの正味の増加距離(m) + const netIncrease = currentDistance - nearestDistance; if ( consecutiveIncreaseCountRef.current >= WRONG_DIRECTION_CONSECUTIVE_COUNT && - cumulativeIncreaseRef.current >= WRONG_DIRECTION_MIN_DISTANCE + netIncrease >= WRONG_DIRECTION_MIN_DISTANCE ) { // 同一のnextStationに対して既に通知済みならスキップ if ( From 48e083904201927fe93b782af3aa2e83c1a1819e Mon Sep 17 00:00:00 2001 From: Tsubasa SEKIGUCHI Date: Tue, 15 Sep 2026 08:05:57 +0900 Subject: [PATCH 2/3] =?UTF-8?q?VITS=E3=81=AE=E6=8E=A8=E8=AB=96=E5=91=BC?= =?UTF-8?q?=E3=81=B3=E5=87=BA=E3=81=97=E3=81=A7=E5=85=A5=E5=8A=9B=E9=85=8D?= =?UTF-8?q?=E5=88=97=E3=81=B8=E3=81=AE=E6=8E=92=E4=BB=96=E3=82=A2=E3=82=AF?= =?UTF-8?q?=E3=82=BB=E3=82=B9=E9=81=95=E5=8F=8D=E3=82=92=E8=A7=A3=E6=B6=88?= =?UTF-8?q?=E3=81=97=E3=81=A6iOS=E3=83=93=E3=83=AB=E3=83=89=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#6980)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01VsdEpyHiRn5gzpRbQeGL7Q Co-authored-by: Claude Opus 5 --- ios/Modules/VitsTTS/VitsTTSModule.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Modules/VitsTTS/VitsTTSModule.swift b/ios/Modules/VitsTTS/VitsTTSModule.swift index f1d7760fe8..b20a704d37 100644 --- a/ios/Modules/VitsTTS/VitsTTSModule.swift +++ b/ios/Modules/VitsTTS/VitsTTSModule.swift @@ -360,7 +360,7 @@ final class VitsTTSModule: NSObject { try outputs.withUnsafeMutableBufferPointer { outValues in try check( api.pointee.Run!( - session, nil, inNames.baseAddress, inValues.baseAddress, inputs.count, + session, nil, inNames.baseAddress, inValues.baseAddress, inValues.count, outNames.baseAddress, outputNameArgs.count, outValues.baseAddress), "Run") } From f889a9d270822575b130a5c17020af7ff3023252 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 08:06:45 +0900 Subject: [PATCH 3/3] Bump version for canary release (#6979) Co-authored-by: TinyKitten <32848922+TinyKitten@users.noreply.github.com> --- android/app/build.gradle | 6 ++--- android/wearable/build.gradle.kts | 6 ++--- app.config.ts | 4 +-- ios/TrainLCD.xcodeproj/project.pbxproj | 36 +++++++++++++------------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index fc200206d0..896b7c0e57 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -161,12 +161,12 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.dev" versionNameSuffix "-dev" - versionCode 100000761 + versionCode 100000763 versionName "10.15.1" } prod { dimension "environment" - versionCode 100000761 + versionCode 100000763 versionName "10.15.1" } // ストア配信する dev (Canary) と applicationId を分けた、ローカル検証専用のフレーバー。 @@ -177,7 +177,7 @@ android { dimension "environment" applicationId "me.tinykitten.trainlcd.local" versionNameSuffix "-local" - versionCode 100000761 + versionCode 100000763 versionName "10.15.1" } } diff --git a/android/wearable/build.gradle.kts b/android/wearable/build.gradle.kts index f5f67fb2ce..2f8c3d6aab 100644 --- a/android/wearable/build.gradle.kts +++ b/android/wearable/build.gradle.kts @@ -85,12 +85,12 @@ android { dimension = "environment" applicationIdSuffix = ".dev" versionNameSuffix = "-dev" - versionCode = 100000762 + versionCode = 100000764 versionName = "10.15.1" } create("prod") { dimension = "environment" - versionCode = 100000762 + versionCode = 100000764 versionName = "10.15.1" } // :app の local フレーバーと対になるローカル検証専用フレーバー。Wear の Data Layer は @@ -100,7 +100,7 @@ android { dimension = "environment" applicationIdSuffix = ".local" versionNameSuffix = "-local" - versionCode = 100000762 + versionCode = 100000764 versionName = "10.15.1" } } diff --git a/app.config.ts b/app.config.ts index 44c9df2212..d7e6f77d63 100644 --- a/app.config.ts +++ b/app.config.ts @@ -57,7 +57,7 @@ export default { userInterfaceStyle: 'automatic', // Expo SDK 57 の各モジュール(expo / expo-modules-core ほか)は podspec で iOS 16.4 以上を要求する deploymentTarget: '16.4', - buildNumber: '2910', + buildNumber: '2911', scheme: IS_DEV ? 'CanaryTrainLCD' : 'ProdTrainLCD', bundleIdentifier: IS_DEV ? 'me.tinykitten.trainlcd.dev' @@ -71,7 +71,7 @@ export default { ? 'me.tinykitten.trainlcd.dev' : 'me.tinykitten.trainlcd', permissions: [], - versionCode: 100000761, + versionCode: 100000763, }, owner: 'trainlcd', experiments: { diff --git a/ios/TrainLCD.xcodeproj/project.pbxproj b/ios/TrainLCD.xcodeproj/project.pbxproj index 7e21982d8b..dff3abb1ca 100644 --- a/ios/TrainLCD.xcodeproj/project.pbxproj +++ b/ios/TrainLCD.xcodeproj/project.pbxproj @@ -2550,7 +2550,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; @@ -2590,7 +2590,7 @@ CODE_SIGN_ENTITLEMENTS = ProdTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Prod/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = TrainLCD; @@ -2649,7 +2649,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -2756,7 +2756,7 @@ CODE_SIGN_ENTITLEMENTS = TrainLCD/trainlcd.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; CXX = "$(REACT_NATIVE_PATH)/scripts/xcode/ccache-clang++.sh"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2837,7 +2837,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -2877,7 +2877,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryTrainLCD.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; INFOPLIST_FILE = TrainLCD/Schemes/Dev/Info.plist; @@ -3089,7 +3089,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/CanaryRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3141,7 +3141,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3192,7 +3192,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/ProdWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3250,7 +3250,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3301,7 +3301,7 @@ CODE_SIGN_ENTITLEMENTS = WatchWidget/CanaryWatchWidget.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3358,7 +3358,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -3407,7 +3407,7 @@ CODE_SIGN_ENTITLEMENTS = RideSessionActivity/ProdRideSessionActivity.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3459,7 +3459,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -3678,7 +3678,7 @@ CODE_SIGN_ENTITLEMENTS = ProdAppClip/ProdAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3734,7 +3734,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3784,7 +3784,7 @@ CODE_SIGN_ENTITLEMENTS = CanaryAppClip/CanaryAppClip.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -3842,7 +3842,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 2910; + CURRENT_PROJECT_VERSION = 2911; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = E6R2G33Z36; ENABLE_USER_SCRIPT_SANDBOXING = NO;